Merge lp:~thisfred/u1db/u1todo-design-1 into lp:u1db

Proposed by Eric Casteleijn on 2012-08-01
Status: Merged
Approved by: Eric Casteleijn on 2012-08-02
Approved revision: 289
Merged at revision: 369
Proposed branch: lp:~thisfred/u1db/u1todo-design-1
Merge into: lp:u1db
Diff against target: 1407 lines (+627/-411)
5 files modified
cosas/cosas.py (+32/-46)
cosas/cosas.ui (+199/-156)
cosas/sync.ui (+131/-0)
cosas/test_cosas.py (+34/-37)
cosas/ui.py (+231/-172)
To merge this branch: bzr merge lp:~thisfred/u1db/u1todo-design-1
Reviewer Review Type Date Requested Status
Roberto Alsina (community) 2012-08-01 Approve on 2012-08-02
Review via email: mp+117796@code.launchpad.net

Commit Message

- renamed u1todo to cosas
- added minimal design to cosas

Description of the Change

- renamed u1todo to cosas
- added minimal design to cosas

To post a comment you must log in.
Roberto Alsina (ralsina) wrote :

Nice!

review: Approve
Ubuntu One Auto Pilot (otto-pilot) wrote :
Download full text (10.8 KiB)

The attempt to merge lp:~thisfred/u1db/u1todo-design-1 into lp:u1db failed. Below is the output from the failed tests.

-- The C compiler identification is GNU 4.7.1
-- The CXX compiler identification is GNU 4.7.1
-- Check for working C compiler: /usr/bin/gcc
-- Check for working C compiler: /usr/bin/gcc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Found Cython: /usr/bin/cython
-- Found CURL: /usr/lib/x86_64-linux-gnu/libcurl.so (found version "7.26.0")
-- Found PkgConfig: /usr/bin/pkg-config (found version "0.26")
-- checking for module 'oauth'
-- found oauth, version 0.9.7
-- Found OAuth: /usr/lib/x86_64-linux-gnu/liboauth.so
-- checking for module 'json'
-- found json, version 0.9
-- Found JSON: /usr/lib/x86_64-linux-gnu/libjson.so
-- checking for module 'sqlite3'
-- found sqlite3, version 3.7.13
-- Found Sqlite3: /usr/lib/x86_64-linux-gnu/libsqlite3.so
-- Configuring done
-- Generating done
-- Build files have been written to: /mnt/tarmac/cache/u1db/trunk
[ 11%] Generating u1db_schema.c
Scanning dependencies of target u1db
[ 22%] Building C object src/CMakeFiles/u1db.dir/mkstemp_compat.c.o
[ 33%] Building C object src/CMakeFiles/u1db.dir/u1db.c.o
[ 44%] Building C object src/CMakeFiles/u1db.dir/u1db_http_sync_target.c.o
[ 55%] Building C object src/CMakeFiles/u1db.dir/u1db_query.c.o
[ 66%] Building C object src/CMakeFiles/u1db.dir/u1db_sync_target.c.o
[ 77%] Building C object src/CMakeFiles/u1db.dir/u1db_uuid.c.o
[ 88%] Building C object src/CMakeFiles/u1db.dir/u1db_vectorclock.c.o
[100%] Building C object src/CMakeFiles/u1db.dir/u1db_schema.c.o
Linking C static library libu1db.a
[100%] Built target u1db
Scanning dependencies of target ReplicatePythonSourceTree
[100%] Built target ReplicatePythonSourceTree
Scanning dependencies of target build-debug
running build_ext
cythoning u1db/tests/c_backend_wrapper.pyx to u1db/tests/c_backend_wrapper.c
building 'u1db.tests.c_backend_wrapper' extension
creating build
creating build/temp.linux-x86_64-2.7-pydebug
creating build/temp.linux-x86_64-2.7-pydebug/u1db
creating build/temp.linux-x86_64-2.7-pydebug/u1db/tests
gcc -pthread -fno-strict-aliasing -g -O0 -Wall -Wstrict-prototypes -fPIC -Iinclude -I/mnt/tarmac/cache/u1db/trunk/include -I/usr/include/python2.7_d -c u1db/tests/c_backend_wrapper.c -o build/temp.linux-x86_64-2.7-pydebug/u1db/tests/c_backend_wrapper.o
gcc -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions -Wl,-Bsymbolic-functions -Wl,-z,relro build/temp.linux-x86_64-2.7-pydebug/u1db/tests/c_backend_wrapper.o -Lsrc -lu1db -lsqlite3 -loauth -lcurl -ljson -o /mnt/tarmac/cache/u1db/trunk/u1db/tests/c_backend_wrapper_d.so
[100%] Built target build-debug
Scanning dependencies of target check-valgrind
Tests running...
======================================================================
ERROR: cosas.test_cosas.TaskTestCase.test_set_done
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/mnt/tarmac/...

lp:~thisfred/u1db/u1todo-design-1 updated on 2012-08-02
289. By Eric Casteleijn on 2012-08-02

fixed tests and exposed bug

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== renamed directory 'u1todo' => 'cosas'
2=== renamed file 'u1todo/u1todo.py' => 'cosas/cosas.py'
3--- u1todo/u1todo.py 2012-07-19 19:50:58 +0000
4+++ cosas/cosas.py 2012-08-02 13:17:19 +0000
5@@ -14,7 +14,7 @@
6 # You should have received a copy of the GNU Lesser General Public License
7 # along with u1db. If not, see <http://www.gnu.org/licenses/>.
8
9-"""u1todo example application."""
10+"""cosas example application."""
11
12 import json
13 import os
14@@ -28,8 +28,8 @@
15 TAGS_INDEX = 'tags'
16 DONE_INDEX = 'done'
17 INDEXES = {
18- TAGS_INDEX: 'tags',
19- DONE_INDEX: 'bool(done)',
20+ TAGS_INDEX: ['tags'],
21+ DONE_INDEX: ['bool(done)'],
22 }
23
24 TAGS = re.compile('#(\w+)|\[(.+)\]')
25@@ -37,9 +37,12 @@
26
27 def get_database():
28 """Get the path that the database is stored in."""
29+
30+ # setting document_factory to Task means that any database method that
31+ # would return documents, now returns Tasks instead.
32 return u1db.open(
33- os.path.join(save_data_path("u1todo"),
34- "u1todo.u1db"), create=True)
35+ os.path.join(save_data_path("cosas"), "cosas.u1db"), create=True,
36+ document_factory=Task)
37
38
39 def extract_tags(text):
40@@ -63,7 +66,7 @@
41 for name, expression in INDEXES.items():
42 if name not in db_indexes:
43 # The index does not yet exist.
44- self.db.create_index(name, expression)
45+ self.db.create_index(name, *expression)
46 continue
47 if expression == db_indexes[name]:
48 # The index exists and is up to date.
49@@ -71,7 +74,7 @@
50 # The index exists but the definition is not what expected, so we
51 # delete it and add the proper index expression.
52 self.db.delete_index(name)
53- self.db.create_index(name, expression)
54+ self.db.create_index(name, *expression)
55
56 def tag_task(self, task, tags):
57 """Set the tags of a task."""
58@@ -106,24 +109,24 @@
59 # documents with all tags.
60 return []
61 # Wrap each document in results in a Task object, and return them.
62- return [Task(doc) for doc in results.values()]
63+ return results.values()
64
65- def get_task(self, task_id):
66+ def get_task(self, doc_id):
67 """Get a task from the database."""
68- document = self.db.get_doc(task_id)
69- if document is None:
70+ task = self.db.get_doc(doc_id)
71+ if task is None:
72 # No document with that id exists in the database.
73- raise KeyError("No task with id '%s'." % (task_id,))
74- if document.is_tombstone():
75+ raise KeyError("No task with id '%s'." % (doc_id,))
76+ if task.is_tombstone():
77 # The document id exists, but the document's content was previously
78 # deleted.
79- raise KeyError("Task with id %s was deleted." % (task_id,))
80+ raise KeyError("Task with id %s was deleted." % (doc_id,))
81 # Wrap the document in a Task object and return it.
82- return Task(document)
83+ return task
84
85 def delete_task(self, task):
86 """Delete a task from the database."""
87- self.db.delete_doc(task._document)
88+ self.db.delete_doc(task)
89
90 def new_task(self, title=None, tags=None):
91 """Create a new task document."""
92@@ -144,69 +147,52 @@
93 # Store the document in the database. Since we did not set a document
94 # id, the database will store it as a new document, and generate
95 # a valid id.
96- document = self.db.create_doc_from_json(content)
97+ task = self.db.create_doc_from_json(content)
98 # Wrap the document in a Task object.
99- return Task(document)
100+ return task
101
102 def save_task(self, task):
103 """Save task to the database."""
104 # Get the u1db document from the task object, and save it to the
105 # database.
106- self.db.put_doc(task.document)
107+ self.db.put_doc(task)
108
109 def get_all_tasks(self):
110 # Since the DONE_INDEX indexes anything that has a value in the field
111 # "done", and all tasks do (either True or False), it's a good way to
112 # get all tasks out of the database.
113- return [Task(doc) for doc in self.db.get_from_index(DONE_INDEX, "*")]
114-
115-
116-class Task(object):
117+ return self.db.get_from_index(DONE_INDEX, "*")
118+
119+
120+class Task(u1db.Document):
121 """A todo item."""
122
123- def __init__(self, document):
124- self._document = document
125-
126- @property
127- def task_id(self):
128- """The u1db id of the task."""
129- # Since we won't ever change this, but it's handy for comparing
130- # different Task objects, it's a read only property.
131- return self._document.doc_id
132-
133 def _get_title(self):
134 """Get the task title."""
135- return self._document.content['title']
136+ return self.content['title']
137
138 def _set_title(self, title):
139 """Set the task title."""
140- self._document.content['title'] = title
141+ self.content['title'] = title
142
143 title = property(_get_title, _set_title, doc="Title of the task.")
144
145 def _get_done(self):
146 """Get the status of the task."""
147- return self._document.content['done']
148+ return self.content['done']
149
150 def _set_done(self, value):
151 """Set the done status."""
152- self._document.content['done'] = value
153+ self.content['done'] = value
154
155 done = property(_get_done, _set_done, doc="Done flag.")
156
157 def _get_tags(self):
158 """Get tags associated with the task."""
159- return self._document.content['tags']
160+ return self.content['tags']
161
162 def _set_tags(self, tags):
163 """Set tags associated with the task."""
164- self._document.content['tags'] = list(set(tags))
165+ self.content['tags'] = list(set(tags))
166
167 tags = property(_get_tags, _set_tags, doc="Task tags.")
168-
169- @property
170- def document(self):
171- """The u1db document representing this task."""
172- # This brings the underlying document's JSON content back into sync
173- # with whatever data the task currently holds.
174- return self._document
175
176=== renamed file 'u1todo/u1todo.ui' => 'cosas/cosas.ui'
177--- u1todo/u1todo.ui 2012-05-08 18:27:32 +0000
178+++ cosas/cosas.ui 2012-08-02 13:17:19 +0000
179@@ -6,175 +6,218 @@
180 <rect>
181 <x>0</x>
182 <y>0</y>
183- <width>328</width>
184- <height>349</height>
185+ <width>329</width>
186+ <height>586</height>
187 </rect>
188 </property>
189+ <property name="sizePolicy">
190+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
191+ <horstretch>0</horstretch>
192+ <verstretch>0</verstretch>
193+ </sizepolicy>
194+ </property>
195 <property name="windowTitle">
196- <string>u1todo</string>
197+ <string>cosas</string>
198+ </property>
199+ <property name="styleSheet">
200+ <string notr="true">QToolButton {
201+ background-color: rgb(203,203,203);
202+ border: 1px solid rgb(102, 102, 102);
203+}
204+
205+QFrame#drop_frame{
206+ border: 0px;
207+ border-top: 2px dotted rgb(102,102,102);
208+ border-bottom: 2px dotted rgb(102,102,102);
209+}
210+
211+QPushButton {
212+ background-color: rgb(61, 133, 198);
213+ color: white;
214+ border: 2px solid rgb(7, 55, 99);
215+ border-radius: 6 6 px;
216+ padding: 5 px;
217+}
218+
219+QComboBox {
220+ background-color: rgb(204, 204, 204);
221+ color: black;
222+ border: 2px solid rgb(102,102,102);
223+ border-radius: 6 6 px;
224+ padding: 5 px;
225+}
226+
227+QFrame#buttons_frame QPushButton {
228+ background-color: rgb(204, 204, 204);
229+ color: black;
230+ border: 2px solid rgb(102,102,102);
231+ border-radius: 0px;
232+}
233+
234+QLineEdit, QTextEdit {
235+ background-color: white;
236+ border-radius: 0;
237+ border: 1px solid rgb(102,102,102);
238+}
239+
240+*[foo=&quot;bar&quot;] {color:red;}
241+</string>
242+ </property>
243+ <property name="toolButtonStyle">
244+ <enum>Qt::ToolButtonFollowStyle</enum>
245+ </property>
246+ <property name="documentMode">
247+ <bool>false</bool>
248 </property>
249 <widget class="QWidget" name="centralwidget">
250- <layout class="QVBoxLayout" name="verticalLayout_3">
251- <item>
252- <widget class="QTabWidget" name="tabWidget">
253- <property name="currentIndex">
254+ <layout class="QHBoxLayout" name="horizontalLayout_2">
255+ <property name="spacing">
256+ <number>0</number>
257+ </property>
258+ <property name="margin">
259+ <number>0</number>
260+ </property>
261+ <item>
262+ <widget class="QFrame" name="buttons_frame">
263+ <layout class="QVBoxLayout" name="tag_buttons">
264+ <property name="spacing">
265+ <number>0</number>
266+ </property>
267+ <property name="margin">
268+ <number>0</number>
269+ </property>
270+ <item>
271+ <layout class="QVBoxLayout" name="buttons_layout">
272+ <item>
273+ <spacer name="verticalSpacer">
274+ <property name="orientation">
275+ <enum>Qt::Vertical</enum>
276+ </property>
277+ <property name="sizeHint" stdset="0">
278+ <size>
279+ <width>20</width>
280+ <height>40</height>
281+ </size>
282+ </property>
283+ </spacer>
284+ </item>
285+ </layout>
286+ </item>
287+ </layout>
288+ </widget>
289+ </item>
290+ <item>
291+ <widget class="QFrame" name="frame_2">
292+ <property name="frameShape">
293+ <enum>QFrame::NoFrame</enum>
294+ </property>
295+ <property name="frameShadow">
296+ <enum>QFrame::Plain</enum>
297+ </property>
298+ <property name="lineWidth">
299 <number>0</number>
300 </property>
301- <widget class="QWidget" name="tab_3">
302- <attribute name="title">
303- <string>u1&amp;todo</string>
304- </attribute>
305- <layout class="QVBoxLayout" name="verticalLayout_2">
306- <item>
307- <layout class="QHBoxLayout" name="horizontalLayout_2">
308- <item>
309- <layout class="QVBoxLayout" name="verticalLayout">
310- <item>
311- <widget class="QListWidget" name="todo_list"/>
312- </item>
313- <item>
314- <widget class="QLineEdit" name="task_edit"/>
315- </item>
316- <item>
317- <layout class="QHBoxLayout" name="horizontalLayout">
318- <item>
319- <widget class="QPushButton" name="edit_button">
320- <property name="text">
321- <string>Add/&amp;Edit</string>
322- </property>
323- </widget>
324- </item>
325- <item>
326- <widget class="QPushButton" name="delete_button">
327- <property name="text">
328- <string>&amp;Delete</string>
329- </property>
330- </widget>
331- </item>
332- </layout>
333- </item>
334- </layout>
335- </item>
336- <item>
337- <layout class="QVBoxLayout" name="tag_buttons">
338- <item>
339- <spacer name="verticalSpacer">
340- <property name="orientation">
341- <enum>Qt::Vertical</enum>
342- </property>
343- <property name="sizeHint" stdset="0">
344- <size>
345- <width>20</width>
346- <height>40</height>
347- </size>
348- </property>
349- </spacer>
350- </item>
351- </layout>
352- </item>
353- </layout>
354- </item>
355- </layout>
356- </widget>
357- <widget class="QWidget" name="tab_4">
358- <attribute name="title">
359- <string>&amp;sync</string>
360- </attribute>
361- <layout class="QVBoxLayout" name="verticalLayout_4">
362- <item>
363- <widget class="QGroupBox" name="target_group">
364- <property name="title">
365- <string>Synchronization target</string>
366- </property>
367- <layout class="QVBoxLayout" name="verticalLayout_5">
368- <item>
369- <widget class="QRadioButton" name="u1_radio">
370- <property name="text">
371- <string>Ubuntu One</string>
372- </property>
373- <property name="checked">
374- <bool>true</bool>
375- </property>
376- <attribute name="buttonGroup">
377- <string notr="true">sync_targets</string>
378- </attribute>
379- </widget>
380- </item>
381- <item>
382- <layout class="QHBoxLayout" name="horizontalLayout_3">
383- <item>
384- <widget class="QRadioButton" name="url_radio">
385- <property name="text">
386- <string>url:</string>
387- </property>
388- <attribute name="buttonGroup">
389- <string notr="true">sync_targets</string>
390- </attribute>
391- </widget>
392- </item>
393- <item>
394- <widget class="QLineEdit" name="url_edit"/>
395- </item>
396- </layout>
397- </item>
398- <item>
399- <layout class="QHBoxLayout" name="horizontalLayout_4">
400- <item>
401- <widget class="QLabel" name="label">
402- <property name="text">
403- <string>Last Synced:</string>
404- </property>
405- </widget>
406- </item>
407- <item>
408- <widget class="QLabel" name="last_synced">
409- <property name="text">
410- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
411- </property>
412- </widget>
413- </item>
414- </layout>
415- </item>
416- </layout>
417- </widget>
418- </item>
419- <item>
420- <widget class="QPushButton" name="sync_button">
421+ <layout class="QVBoxLayout" name="verticalLayout">
422+ <property name="spacing">
423+ <number>0</number>
424+ </property>
425+ <property name="margin">
426+ <number>0</number>
427+ </property>
428+ <item>
429+ <layout class="QHBoxLayout" name="horizontalLayout_5">
430+ <property name="margin">
431+ <number>6</number>
432+ </property>
433+ <item>
434+ <widget class="QToolButton" name="buttons_toggle">
435+ <property name="autoFillBackground">
436+ <bool>false</bool>
437+ </property>
438+ <property name="text">
439+ <string/>
440+ </property>
441+ <property name="checkable">
442+ <bool>true</bool>
443+ </property>
444+ <property name="autoRaise">
445+ <bool>false</bool>
446+ </property>
447+ <property name="arrowType">
448+ <enum>Qt::LeftArrow</enum>
449+ </property>
450+ </widget>
451+ </item>
452+ <item>
453+ <widget class="QLineEdit" name="title_edit">
454+ <property name="text">
455+ <string>Create a new task</string>
456+ </property>
457+ </widget>
458+ </item>
459+ </layout>
460+ </item>
461+ <item>
462+ <widget class="QTreeWidget" name="todo_list">
463+ <property name="frameShape">
464+ <enum>QFrame::StyledPanel</enum>
465+ </property>
466+ <property name="frameShadow">
467+ <enum>QFrame::Plain</enum>
468+ </property>
469+ <property name="indentation">
470+ <number>0</number>
471+ </property>
472+ <property name="uniformRowHeights">
473+ <bool>true</bool>
474+ </property>
475+ <property name="animated">
476+ <bool>true</bool>
477+ </property>
478+ <property name="columnCount">
479+ <number>1</number>
480+ </property>
481+ <property name="topLevelItemCount" stdset="0">
482+ <number>0</number>
483+ </property>
484+ <attribute name="headerVisible">
485+ <bool>false</bool>
486+ </attribute>
487+ <column>
488 <property name="text">
489- <string>Syn&amp;chronize</string>
490- </property>
491- </widget>
492- </item>
493- <item>
494- <spacer name="verticalSpacer_2">
495- <property name="orientation">
496- <enum>Qt::Vertical</enum>
497- </property>
498- <property name="sizeHint" stdset="0">
499- <size>
500- <width>20</width>
501- <height>40</height>
502- </size>
503- </property>
504- </spacer>
505- </item>
506- </layout>
507- </widget>
508+ <string>title</string>
509+ </property>
510+ </column>
511+ </widget>
512+ </item>
513+ </layout>
514 </widget>
515 </item>
516 </layout>
517 </widget>
518- <widget class="QStatusBar" name="statusbar"/>
519+ <widget class="QMenuBar" name="menuBar">
520+ <property name="geometry">
521+ <rect>
522+ <x>0</x>
523+ <y>0</y>
524+ <width>329</width>
525+ <height>21</height>
526+ </rect>
527+ </property>
528+ <widget class="QMenu" name="menu_File">
529+ <property name="title">
530+ <string>&amp;File</string>
531+ </property>
532+ <addaction name="action_synchronize"/>
533+ </widget>
534+ <addaction name="menu_File"/>
535+ </widget>
536+ <action name="action_synchronize">
537+ <property name="text">
538+ <string>&amp;Synchronize</string>
539+ </property>
540+ </action>
541 </widget>
542- <tabstops>
543- <tabstop>task_edit</tabstop>
544- <tabstop>edit_button</tabstop>
545- <tabstop>delete_button</tabstop>
546- <tabstop>todo_list</tabstop>
547- </tabstops>
548 <resources/>
549 <connections/>
550- <buttongroups>
551- <buttongroup name="sync_targets"/>
552- </buttongroups>
553 </ui>
554
555=== added file 'cosas/sync.ui'
556--- cosas/sync.ui 1970-01-01 00:00:00 +0000
557+++ cosas/sync.ui 2012-08-02 13:17:19 +0000
558@@ -0,0 +1,131 @@
559+<?xml version="1.0" encoding="UTF-8"?>
560+<ui version="4.0">
561+ <class>Dialog</class>
562+ <widget class="QDialog" name="Dialog">
563+ <property name="geometry">
564+ <rect>
565+ <x>0</x>
566+ <y>0</y>
567+ <width>329</width>
568+ <height>169</height>
569+ </rect>
570+ </property>
571+ <property name="windowTitle">
572+ <string>synchronize</string>
573+ </property>
574+ <property name="styleSheet">
575+ <string notr="true">QToolButton {
576+ background-color: rgb(203,203,203);
577+ border: 1px solid rgb(102, 102, 102);
578+}
579+
580+QFrame#drop_frame{
581+ border: 0px;
582+ border-top: 2px dotted rgb(102,102,102);
583+ border-bottom: 2px dotted rgb(102,102,102);
584+}
585+
586+QPushButton {
587+ background-color: rgb(61, 133, 198);
588+ color: white;
589+ border: 2px solid rgb(7, 55, 99);
590+ border-radius: 6 6 px;
591+ padding: 5 px;
592+}
593+
594+QComboBox {
595+ background-color: rgb(204, 204, 204);
596+ color: black;
597+ border: 2px solid rgb(102,102,102);
598+ border-radius: 6 6 px;
599+ padding: 5 px;
600+}
601+
602+QFrame#buttons_frame QPushButton {
603+ background-color: rgb(204, 204, 204);
604+ color: black;
605+ border: 2px solid rgb(102,102,102);
606+ border-radius: 0px;
607+}
608+
609+QTreeView::item{
610+ border: 1px dotted rgb(102, 102, 102);
611+ border-top: 0px;
612+ background-color: white;
613+}
614+
615+QLineEdit, QTextEdit {
616+ background-color: white;
617+ border-radius: 0;
618+ border: 1px solid rgb(102,102,102);
619+}
620+
621+* {
622+ background-color: rgb(239, 239, 239);
623+}
624+</string>
625+ </property>
626+ <layout class="QVBoxLayout" name="verticalLayout">
627+ <item>
628+ <widget class="QGroupBox" name="target_group">
629+ <property name="title">
630+ <string>Synchronization target</string>
631+ </property>
632+ <layout class="QVBoxLayout" name="verticalLayout_5">
633+ <item>
634+ <widget class="QRadioButton" name="u1_radio">
635+ <property name="text">
636+ <string>Ubuntu One</string>
637+ </property>
638+ <property name="checked">
639+ <bool>true</bool>
640+ </property>
641+ </widget>
642+ </item>
643+ <item>
644+ <layout class="QHBoxLayout" name="horizontalLayout_3">
645+ <item>
646+ <widget class="QRadioButton" name="url_radio">
647+ <property name="text">
648+ <string>url:</string>
649+ </property>
650+ </widget>
651+ </item>
652+ <item>
653+ <widget class="QLineEdit" name="url_edit"/>
654+ </item>
655+ </layout>
656+ </item>
657+ <item>
658+ <layout class="QHBoxLayout" name="horizontalLayout_4">
659+ <item>
660+ <widget class="QLabel" name="label">
661+ <property name="text">
662+ <string>Last Synced:</string>
663+ </property>
664+ </widget>
665+ </item>
666+ <item>
667+ <widget class="QLabel" name="last_synced">
668+ <property name="text">
669+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
670+ </property>
671+ </widget>
672+ </item>
673+ </layout>
674+ </item>
675+ </layout>
676+ </widget>
677+ </item>
678+ <item>
679+ <widget class="QPushButton" name="sync_button">
680+ <property name="text">
681+ <string>Syn&amp;chronize</string>
682+ </property>
683+ </widget>
684+ </item>
685+ </layout>
686+ </widget>
687+ <resources/>
688+ <connections/>
689+</ui>
690
691=== renamed file 'u1todo/test_u1todo.py' => 'cosas/test_cosas.py'
692--- u1todo/test_u1todo.py 2012-07-19 19:50:58 +0000
693+++ cosas/test_cosas.py 2012-08-02 13:17:19 +0000
694@@ -14,10 +14,10 @@
695 # You should have received a copy of the GNU Lesser General Public License
696 # along with u1db. If not, see <http://www.gnu.org/licenses/>.
697
698-"""Tests for u1todo example application."""
699+"""Tests for cosas example application."""
700
701 from testtools import TestCase
702-from u1todo import (
703+from cosas import (
704 Task, TodoStore, INDEXES, TAGS_INDEX, EMPTY_TASK, extract_tags)
705 from u1db.backends import inmemory
706
707@@ -26,14 +26,15 @@
708
709 def setUp(self):
710 super(TodoStoreTestCase, self).setUp()
711- self.db = inmemory.InMemoryDatabase("u1todo")
712+ self.db = inmemory.InMemoryDatabase("cosas")
713+ self.db.set_document_factory(Task)
714
715 def test_initialize_db(self):
716 """Creates indexes."""
717 store = TodoStore(self.db)
718 store.initialize_db()
719 for key, value in self.db.list_indexes():
720- self.assertEqual(INDEXES[key], value[0])
721+ self.assertEqual(INDEXES[key], value)
722
723 def test_reinitialize_db(self):
724 """Creates indexes."""
725@@ -41,13 +42,13 @@
726 store.new_task()
727 store.initialize_db()
728 for key, value in self.db.list_indexes():
729- self.assertEqual(INDEXES[key], value[0])
730+ self.assertEqual(INDEXES[key], value)
731
732 def test_indexes_are_added(self):
733 """New indexes are added when a new store is created."""
734 store = TodoStore(self.db)
735 store.initialize_db()
736- INDEXES['foo'] = 'bar'
737+ INDEXES['foo'] = ['bar']
738 self.assertNotIn('foo', dict(self.db.list_indexes()))
739 store = TodoStore(self.db)
740 store.initialize_db()
741@@ -58,7 +59,7 @@
742 store = TodoStore(self.db)
743 store.initialize_db()
744 new_expression = 'newtags'
745- INDEXES[TAGS_INDEX] = new_expression
746+ INDEXES[TAGS_INDEX] = [new_expression]
747 self.assertNotEqual(
748 new_expression, dict(self.db.list_indexes())['tags'])
749 store = TodoStore(self.db)
750@@ -95,14 +96,14 @@
751 tags2 = ['foo', 'sball']
752 task2 = store.new_task(tags=tags2)
753 self.assertEqual(
754- sorted([task1.task_id, task2.task_id]),
755- sorted([t.task_id for t in store.get_tasks_by_tags(['foo'])]))
756- self.assertEqual(
757- [task1.task_id],
758- [t.task_id for t in store.get_tasks_by_tags(['foo', 'bar'])])
759- self.assertEqual(
760- [task2.task_id],
761- [t.task_id for t in store.get_tasks_by_tags(['foo', 'sball'])])
762+ sorted([task1.doc_id, task2.doc_id]),
763+ sorted([t.doc_id for t in store.get_tasks_by_tags(['foo'])]))
764+ self.assertEqual(
765+ [task1.doc_id],
766+ [t.doc_id for t in store.get_tasks_by_tags(['foo', 'bar'])])
767+ self.assertEqual(
768+ [task2.doc_id],
769+ [t.doc_id for t in store.get_tasks_by_tags(['foo', 'sball'])])
770
771 def test_get_tasks_by_empty_tags(self):
772 store = TodoStore(self.db)
773@@ -112,8 +113,8 @@
774 tags2 = ['foo', 'sball']
775 task2 = store.new_task(tags=tags2)
776 self.assertEqual(
777- sorted([task1.task_id, task2.task_id]),
778- sorted([t.task_id for t in store.get_tasks_by_tags([])]))
779+ sorted([task1.doc_id, task2.doc_id]),
780+ sorted([t.doc_id for t in store.get_tasks_by_tags([])]))
781
782 def test_tag_task(self):
783 """Sets the tags for a task."""
784@@ -128,7 +129,7 @@
785 store = TodoStore(self.db)
786 task = store.new_task()
787 self.assertTrue(isinstance(task, Task))
788- self.assertIsNotNone(task.task_id)
789+ self.assertIsNotNone(task.doc_id)
790
791 def test_new_task_with_title(self):
792 """Creates a new task."""
793@@ -150,7 +151,7 @@
794 task = store.new_task()
795 task.title = "This is the title."
796 store.save_task(task)
797- task_copy = store.get_task(task.task_id)
798+ task_copy = store.get_task(task.doc_id)
799 self.assertEqual(task.title, task_copy.title)
800
801 def test_get_non_existant_task(self):
802@@ -163,7 +164,7 @@
803 store = TodoStore(self.db)
804 task = store.new_task()
805 store.delete_task(task)
806- self.assertRaises(KeyError, store.get_task, task.task_id)
807+ self.assertRaises(KeyError, store.get_task, task.doc_id)
808
809 def test_get_all_tasks(self):
810 store = TodoStore(self.db)
811@@ -171,9 +172,9 @@
812 task1 = store.new_task()
813 task2 = store.new_task()
814 task3 = store.new_task()
815- task_ids = [task.task_id for task in store.get_all_tasks()]
816+ task_ids = [task.doc_id for task in store.get_all_tasks()]
817 self.assertEqual(
818- sorted([task1.task_id, task2.task_id, task3.task_id]),
819+ sorted([task1.doc_id, task2.doc_id, task3.doc_id]),
820 sorted(task_ids))
821
822
823@@ -182,34 +183,30 @@
824
825 def setUp(self):
826 super(TaskTestCase, self).setUp()
827- self.db = inmemory.InMemoryDatabase("u1todo")
828+ self.db = inmemory.InMemoryDatabase("cosas")
829+ self.db.set_document_factory(Task)
830 self.document = self.db.create_doc_from_json(EMPTY_TASK)
831
832 def test_task(self):
833 """Initializing a task."""
834- task = Task(self.document)
835+ task = self.document
836 self.assertEqual("", task.title)
837 self.assertEqual([], task.tags)
838 self.assertEqual(False, task.done)
839
840- def test_task_id(self):
841- """Task id is set to document id."""
842- task = Task(self.document)
843- self.assertEqual(self.document.doc_id, task.task_id)
844-
845 def test_set_title(self):
846 """Changing the title is persistent."""
847- task = Task(self.document)
848+ task = self.document
849 title = "new task"
850 task.title = title
851- self.assertEqual(title, task._document.content['title'])
852+ self.assertEqual(title, task.content['title'])
853
854 def test_set_done(self):
855 """Changing the done property changes the underlying content."""
856- task = Task(self.document)
857- self.assertEqual(False, task._document.content['done'])
858+ task = self.document
859+ self.assertEqual(False, task.content['done'])
860 task.done = True
861- self.assertEqual(True, task._document.content['done'])
862+ self.assertEqual(True, task.content['done'])
863
864 def test_extracts_tags(self):
865 """Tags are extracted from the item's text."""
866@@ -218,11 +215,11 @@
867
868 def test_tags(self):
869 """Tags property returns a list."""
870- task = Task(self.document)
871+ task = self.document
872 self.assertEqual([], task.tags)
873
874 def set_tags(self):
875 """Setting the tags property changes the underlying content."""
876- task = Task(self.document)
877+ task = self.document
878 task.tags = ["foo", "bar"]
879- self.assertEqual(["foo", "bar"], task._document.content['tags'])
880+ self.assertEqual(["foo", "bar"], task.content['tags'])
881
882=== modified file 'cosas/ui.py'
883--- u1todo/ui.py 2012-05-09 15:06:46 +0000
884+++ cosas/ui.py 2012-08-02 13:17:19 +0000
885@@ -14,7 +14,7 @@
886 # You should have received a copy of the GNU Lesser General Public License
887 # along with u1db. If not, see <http://www.gnu.org/licenses/>.
888
889-"""User interface for the u1todo example application."""
890+"""User interface for the cosas example application."""
891
892 from collections import defaultdict
893 from datetime import datetime
894@@ -22,7 +22,7 @@
895 import sys
896 from PyQt4 import QtGui, QtCore, uic
897
898-from u1todo import TodoStore, get_database, extract_tags
899+from cosas import TodoStore, get_database, extract_tags
900 import u1db
901 from u1db.errors import DatabaseDoesNotExist
902 from u1db.sync import Synchronizer
903@@ -30,127 +30,114 @@
904 from u1db.remote.http_database import HTTPDatabase
905 from ubuntuone.platform.credentials import CredentialsManagementTool
906
907-
908-class UITask(QtGui.QListWidgetItem):
909+DONE_COLOR = QtGui.QColor(183, 183, 183)
910+NOT_DONE_COLOR = QtGui.QColor(0, 0, 0)
911+WHITE = QtGui.QColor(255, 255, 255)
912+TAG_COLORS = [
913+ (234, 153, 153),
914+ (249, 203, 156),
915+ (255, 229, 153),
916+ (182, 215, 168),
917+ (162, 196, 201),
918+ (164, 194, 244),
919+ (221, 126, 107),
920+ (159, 197, 232),
921+ (180, 167, 214),
922+ (213, 166, 189)]
923+
924+
925+class UITask(QtGui.QTreeWidgetItem):
926 """Task list item."""
927
928- def __init__(self, task):
929- super(UITask, self).__init__()
930+ def __init__(self, task, parent, store, font, main_window):
931+ super(UITask, self).__init__(parent)
932 self.task = task
933- # Set the list item's text to the task's title.
934- self.setText(self.task.title)
935 # If the task is done, check off the list item.
936- self.setCheckState(
937- QtCore.Qt.Checked if task.done else QtCore.Qt.Unchecked)
938- self.update_strikethrough()
939-
940- def update_strikethrough(self):
941- font = self.font()
942- font.setStrikeOut(self.task.done)
943- self.setFont(font)
944-
945-
946-class Main(QtGui.QMainWindow):
947- """Main window of our application."""
948-
949- def __init__(self, in_memory=False):
950- super(Main, self).__init__()
951- # Dynamically load the ui file generated by QtDesigner.
952+ self.store = store
953+ self._bg_color = WHITE
954+ self._font = font
955+ self.main_window = main_window
956+
957+ def set_color(self, color):
958+ self._bg_color = color
959+
960+ def setData(self, column, role, value):
961+ if role == QtCore.Qt.CheckStateRole:
962+ if value == QtCore.Qt.Checked:
963+ self.task.done = True
964+ else:
965+ self.task.done = False
966+ self.store.save_task(self.task)
967+ if role == QtCore.Qt.EditRole:
968+ text = unicode(value.toString(), 'utf-8')
969+ if not text:
970+ # There was no text in the edit field so do nothing.
971+ return
972+ self.update_task_text(text)
973+ super(UITask, self).setData(column, role, value)
974+
975+ def data(self, column, role):
976+ if role == QtCore.Qt.EditRole:
977+ return self.task.title
978+ if role == QtCore.Qt.DisplayRole:
979+ return self.task.title
980+ if role == QtCore.Qt.FontRole:
981+ font = self._font
982+ font.setStrikeOut(self.task.done)
983+ return font
984+ if role == QtCore.Qt.BackgroundRole:
985+ return self._bg_color
986+ if role == QtCore.Qt.ForegroundRole:
987+ return DONE_COLOR if self.task.done else NOT_DONE_COLOR
988+ if role == QtCore.Qt.CheckStateRole:
989+ return QtCore.Qt.Checked if self.task.done else QtCore.Qt.Unchecked
990+ return super(UITask, self).data(column, role)
991+
992+ def update_task_text(self, text):
993+ """Edit an existing todo item."""
994+ # Change the task's title to the text in the edit field.
995+ self.task.title = text
996+ # Record the current tags.
997+ old_tags = set(self.task.tags) if self.task.tags else set([])
998+ # Extract the new tags from the new text.
999+ new_tags = set(extract_tags(text))
1000+ # Check if the tag filter buttons need updating.
1001+ self.main_window.update_tags(self, old_tags, new_tags)
1002+ # Set the tags on the task.
1003+ self.task.tags = list(new_tags)
1004+ # Save the changed task to the database.
1005+ self.store.save_task(self.task)
1006+
1007+
1008+class TaskDelegate(QtGui.QStyledItemDelegate):
1009+ """Delegate for rendering tasks."""
1010+
1011+ pen = QtGui.QPen(QtGui.QColor(102, 102, 102), 2, style=QtCore.Qt.DotLine)
1012+
1013+ def paint(self, painter, option, index):
1014+ # Save current state of painter before we modify anything.
1015+ painter.save()
1016+ painter.setPen(self.pen)
1017+ painter.drawRect(option.rect)
1018+ # Return painter to original stats.
1019+ painter.restore()
1020+ super(TaskDelegate, self).paint(painter, option, index)
1021+
1022+
1023+class Sync(QtGui.QDialog):
1024+
1025+ def __init__(self, other):
1026+ super(Sync, self).__init__()
1027 uifile = os.path.join(
1028- os.path.abspath(os.path.dirname(__file__)), 'u1todo.ui')
1029+ os.path.abspath(os.path.dirname(__file__)), 'sync.ui')
1030 uic.loadUi(uifile, self)
1031- # hook up the signals to the signal handlers.
1032 self.connect_events()
1033- # Load the u1todo database.
1034- db = get_database()
1035- # And wrap it in a TodoStore object.
1036- self.store = TodoStore(db)
1037- # create or update the indexes if they are not up-to-date
1038- self.store.initialize_db()
1039- # Initially the delete button is disabled, because there are no tasks
1040- # to delete.
1041- self.delete_button.setEnabled(False)
1042- # Initialize some variables we will use to keep track of the tags.
1043- self._tag_docs = defaultdict(list)
1044- self._tag_buttons = {}
1045- self._tag_filter = []
1046- # Get all the tasks in the database, and add them to the UI.
1047- for task in self.store.get_all_tasks():
1048- self.add_task(task)
1049- self.task_edit.clear()
1050- # Give the edit field focus.
1051- self.task_edit.setFocus()
1052- # Initialize the variable that points to the currently selected list
1053- # item.
1054- self.item = None
1055+ self.other = other
1056
1057 def connect_events(self):
1058 """Hook up all the signal handlers."""
1059- # On enter, save the task that was being edited.
1060- self.task_edit.returnPressed.connect(self.update)
1061- # When the Edit/Add button is clicked, save the task that was being
1062- # edited.
1063- self.edit_button.clicked.connect(self.update)
1064- # When the Delete button is clicked, delete the currently selected task
1065- # (if any.)
1066- self.delete_button.clicked.connect(self.delete)
1067- # If a new row in the list is selected, change the currently selected
1068- # task, and put its contents in the edit field.
1069- self.todo_list.currentRowChanged.connect(self.row_changed)
1070- # If the checked status of an item in the list changes, change the done
1071- # status of the task.
1072- self.todo_list.itemChanged.connect(self.item_changed)
1073 self.sync_button.clicked.connect(self.synchronize)
1074
1075- def refresh_filter(self):
1076- """Remove all tasks, and show only those that satisfy the new filter.
1077-
1078- """
1079- # Remove everything from the list.
1080- while len(self.todo_list):
1081- self.todo_list.takeItem(0)
1082- # Get the filtered tasks from the database.
1083- for task in self.store.get_tasks_by_tags(self._tag_filter):
1084- # Add them to the UI.
1085- self.add_task(task)
1086- # Clear the current selection.
1087- self.todo_list.setCurrentRow(-1)
1088- self.task_edit.clear()
1089- self.item = None
1090-
1091- def item_changed(self, item):
1092- """Mark a task as done or not done."""
1093- if item.checkState() == QtCore.Qt.Checked:
1094- item.task.done = True
1095- else:
1096- item.task.done = False
1097- # Save the task to the database.
1098- item.update_strikethrough()
1099- item.setText(item.task.title)
1100- self.store.save_task(item.task)
1101- # Clear the current selection.
1102- self.todo_list.setCurrentRow(-1)
1103- self.task_edit.clear()
1104- self.item = None
1105-
1106- def update(self):
1107- """Either add a new task or update an existing one."""
1108- text = unicode(self.task_edit.text(), 'utf-8')
1109- if not text:
1110- # There was no text in the edit field so do nothing.
1111- return
1112- if self.item is None:
1113- # No task was selected, so add a new one.
1114- task = self.store.new_task(text, tags=extract_tags(text))
1115- self.add_task(task)
1116- else:
1117- # A task was selected, so update it.
1118- self.update_task_text(text)
1119- # Clear the current selection.
1120- self.todo_list.setCurrentRow(-1)
1121- self.task_edit.clear()
1122- self.item = None
1123-
1124 def get_ubuntuone_credentials(self):
1125 cmt = CredentialsManagementTool()
1126 return cmt.find_credentials()
1127@@ -167,7 +154,7 @@
1128 def _synchronize(self, creds=None):
1129 if self.u1_radio.isChecked():
1130 # TODO: not hardcode
1131- target = 'https://u1db.one.ubuntu.com/~/u1todo'
1132+ target = 'https://u1db.one.ubuntu.com/~/cosas'
1133 else:
1134 target = self.url_edit.text()
1135 if target.startswith('http://') or target.startswith('https://'):
1136@@ -192,11 +179,105 @@
1137 db.set_oauth_credentials(**oauth_creds)
1138 db.open(create=True)
1139 syncer.sync()
1140- self.refresh_filter()
1141+ self.other.refresh_filter()
1142 self.last_synced.setText(
1143 '<span style="color:green">%s</span>' % (datetime.now()))
1144 self.sync_button.setEnabled(True)
1145
1146+
1147+class Main(QtGui.QMainWindow):
1148+ """Main window of our application."""
1149+
1150+ def __init__(self, in_memory=False):
1151+ super(Main, self).__init__()
1152+ # Dynamically load the ui file generated by QtDesigner.
1153+ uifile = os.path.join(
1154+ os.path.abspath(os.path.dirname(__file__)), 'cosas.ui')
1155+ uic.loadUi(uifile, self)
1156+ # set the model for the treeview
1157+ self.buttons_frame.hide()
1158+ # hook up the signals to the signal handlers.
1159+ self.connect_events()
1160+ # Load the cosas database.
1161+ db = get_database()
1162+ # And wrap it in a TodoStore object.
1163+ self.store = TodoStore(db)
1164+ # create or update the indexes if they are not up-to-date
1165+ self.store.initialize_db()
1166+ # hook up the delegate
1167+ self.todo_list.setItemDelegate(TaskDelegate())
1168+ # Initialize some variables we will use to keep track of the tags.
1169+ self._tag_docs = defaultdict(list)
1170+ self._tag_buttons = {}
1171+ self._tag_filter = []
1172+ self._tag_colors = {}
1173+ # A list of colors to give differently tagged items different colors.
1174+ self.colors = TAG_COLORS[:]
1175+ # Get all the tasks in the database, and add them to the UI.
1176+ for task in self.store.get_all_tasks():
1177+ self.add_task(task)
1178+ self.title_edit.clear()
1179+ # Give the edit field focus.
1180+ self.title_edit.setFocus()
1181+
1182+ def get_tag_color(self):
1183+ """Get a color number to use for a new tag."""
1184+ # Remove a color from the list of available ones and return it.
1185+ if not self.colors:
1186+ return WHITE
1187+ return self.colors.pop(0)
1188+
1189+ def connect_events(self):
1190+ """Hook up all the signal handlers."""
1191+ # On enter, save the task that was being edited.
1192+ self.title_edit.returnPressed.connect(self.update)
1193+ self.action_synchronize.triggered.connect(self.open_sync_window)
1194+ self.buttons_toggle.clicked.connect(self.show_buttons)
1195+
1196+ def open_sync_window(self):
1197+ window = Sync(self)
1198+ window.exec_()
1199+
1200+ def show_buttons(self):
1201+ """Show the frame with the tag buttons."""
1202+ self.buttons_toggle.clicked.disconnect(self.show_buttons)
1203+ self.buttons_frame.show()
1204+ self.buttons_toggle.clicked.connect(self.hide_buttons)
1205+
1206+ def hide_buttons(self):
1207+ """Show the frame with the tag buttons."""
1208+ self.buttons_toggle.clicked.disconnect(self.hide_buttons)
1209+ self.buttons_frame.hide()
1210+ self.buttons_toggle.clicked.connect(self.show_buttons)
1211+
1212+ def refresh_filter(self):
1213+ """Remove all tasks, and show only those that satisfy the new filter.
1214+
1215+ """
1216+ # Remove everything from the list.
1217+ while self.todo_list.topLevelItemCount():
1218+ self.todo_list.takeTopLevelItem(0)
1219+ # Get the filtered tasks from the database.
1220+ for task in self.store.get_tasks_by_tags(self._tag_filter):
1221+ # Add them to the UI.
1222+ self.add_task(task)
1223+ # Clear the current selection.
1224+ self.todo_list.setCurrentItem(None)
1225+ self.title_edit.clear()
1226+ self.item = None
1227+
1228+ def update(self):
1229+ """Either add a new task or update an existing one."""
1230+ text = unicode(self.title_edit.text(), 'utf-8')
1231+ if not text:
1232+ # There was no text in the edit field so do nothing.
1233+ return
1234+ # No task was selected, so add a new one.
1235+ task = self.store.new_task(text, tags=extract_tags(text))
1236+ self.add_task(task)
1237+ # Clear the current selection.
1238+ self.title_edit.clear()
1239+
1240 def delete(self):
1241 """Delete a todo item."""
1242 # Delete the item from the database.
1243@@ -207,41 +288,47 @@
1244 self.store.delete_task(item.task)
1245 # Clear the current selection.
1246 self.todo_list.setCurrentRow(-1)
1247- self.task_edit.clear()
1248+ self.title_edit.clear()
1249 self.item = None
1250- if self.todo_list.count() == 0:
1251- # If there are no tasks left, disable the delete button.
1252- self.delete_button.setEnabled(False)
1253
1254 def add_task(self, task):
1255 """Add a new todo item."""
1256 # Wrap the task in a UITask object.
1257- item = UITask(task)
1258- self.todo_list.addItem(item)
1259- # We know there is at least one item now so we enable the delete
1260- # button.
1261- self.delete_button.setEnabled(True)
1262+ item = UITask(
1263+ task, self.todo_list, self.store, self.todo_list.font(), self)
1264+ item.setFlags(item.flags() | QtCore.Qt.ItemIsEditable)
1265+ self.todo_list.addTopLevelItem(item)
1266 if not task.tags:
1267 return
1268 # If the task has tags, we add them as filter buttons to the UI, if
1269 # they are new.
1270 for tag in task.tags:
1271- self.add_tag(task.task_id, tag)
1272+ self.add_tag(task.doc_id, tag)
1273+ if task.tags:
1274+ item.set_color(self._tag_colors[task.tags[0]]['qcolor'])
1275+ else:
1276+ item.set_color(WHITE)
1277
1278- def add_tag(self, task_id, tag):
1279- """Create a link between the task with id task_id and the tag, and
1280+ def add_tag(self, doc_id, tag):
1281+ """Create a link between the task with id doc_id and the tag, and
1282 add a new button for tag if it was not already there.
1283
1284 """
1285 # Add the task id to the list of document ids associated with this tag.
1286- self._tag_docs[tag].append(task_id)
1287+ self._tag_docs[tag].append(doc_id)
1288 # If the list has more than one element the tag button was already
1289 # present.
1290 if len(self._tag_docs[tag]) > 1:
1291 return
1292 # Add a tag filter button for this tag to the UI.
1293 button = QtGui.QPushButton(tag)
1294- button._u1todo_tag = tag
1295+ color = self.get_tag_color()
1296+ qcolor = QtGui.QColor(*color)
1297+ self._tag_colors[tag] = {
1298+ 'color_tuple': color,
1299+ 'qcolor': qcolor}
1300+ button.setStyleSheet('background-color: rgb(%d, %d, %d)' % color)
1301+ button._todo_tag = tag
1302 # Make the button an on/off button.
1303 button.setCheckable(True)
1304 # Store a reference to the button in a dictionary so we can find it
1305@@ -254,10 +341,10 @@
1306 """Toggle the filter for the tag associated with this button."""
1307 if checked:
1308 # Add the tag to the current filter.
1309- self._tag_filter.append(button._u1todo_tag)
1310+ self._tag_filter.append(button._todo_tag)
1311 else:
1312 # Remove the tag from the current filter.
1313- self._tag_filter.remove(button._u1todo_tag)
1314+ self._tag_filter.remove(button._todo_tag)
1315 # Apply the new filter.
1316 self.refresh_filter()
1317
1318@@ -267,17 +354,17 @@
1319 # sorted alphabetically by the text of the tag.
1320 index = sorted(self._tag_buttons.keys()).index(tag)
1321 # And add the button to the UI.
1322- self.tag_buttons.insertWidget(index, button)
1323+ self.buttons_layout.insertWidget(index, button)
1324
1325- def remove_tag(self, task_id, tag):
1326- """Remove the link between the task with id task_id and the tag, and
1327+ def remove_tag(self, doc_id, tag):
1328+ """Remove the link between the task with id doc_id and the tag, and
1329 remove the button for tag if it no longer has any tasks associated with
1330 it.
1331
1332 """
1333 # Remove the task id from the list of document ids associated with this
1334 # tag.
1335- self._tag_docs[tag].remove(task_id)
1336+ self._tag_docs[tag].remove(doc_id)
1337 # If the list is not empty, we do not remove the button, because there
1338 # are still tasks that have this tag.
1339 if self._tag_docs[tag]:
1340@@ -285,56 +372,28 @@
1341 # Look up the button.
1342 button = self._tag_buttons[tag]
1343 # Remove it from the ui.
1344- self.tag_buttons.removeWidget(button)
1345+ button.hide()
1346+ self.buttons_layout.removeWidget(button)
1347 # And remove the reference.
1348 del self._tag_buttons[tag]
1349
1350- def update_tags(self, task_id, old_tags, new_tags):
1351- """Process any changed tags for this task_id."""
1352+ def update_tags(self, item, old_tags, new_tags):
1353+ """Process any changed tags for this item."""
1354 # Process all removed tags.
1355 for tag in old_tags - new_tags:
1356- self.remove_tag(task_id, tag)
1357+ self.remove_tag(item.task.doc_id, tag)
1358 # Process all tags newly added.
1359 for tag in new_tags - old_tags:
1360- self.add_tag(task_id, tag)
1361-
1362- def update_task_text(self, text):
1363- """Edit an existing todo item."""
1364- item = self.item
1365- task = item.task
1366- # Change the task's title to the text in the edit field.
1367- task.title = text
1368- # Record the current tags.
1369- old_tags = set(task.tags) if task.tags else set([])
1370- # Extract the new tags from the new text.
1371- new_tags = set(extract_tags(text))
1372- # Check if the tag filter buttons need updating.
1373- self.update_tags(item.task.task_id, old_tags, new_tags)
1374- # Set the tags on the task.
1375- task.tags = list(new_tags)
1376- # disconnect the signal temporarily while we change the title
1377- self.todo_list.itemChanged.disconnect(self.item_changed)
1378- # Change the text in the UI.
1379- item.setText(text)
1380- # reconnect the signal after we changed the title
1381- self.todo_list.itemChanged.connect(self.item_changed)
1382- # Save the changed task to the database.
1383- self.store.save_task(task)
1384-
1385- def row_changed(self, index):
1386- """Edit item when row changes."""
1387- if index == -1:
1388- self.task_edit.clear()
1389+ self.add_tag(item.task.doc_id, tag)
1390+ if new_tags:
1391+ item.set_color(self._tag_colors[list(new_tags)[0]]['qcolor'])
1392 return
1393- # If a row is selected, show the selected task's title in the edit
1394- # field.
1395- self.item = self.todo_list.item(index)
1396- self.task_edit.setText(self.item.task.title)
1397+ item.set_color(WHITE)
1398
1399
1400 if __name__ == "__main__":
1401- # Unfortunately, to be able to use ubuntuone.platform.credentials on linux,
1402- # we now depend on dbus. :(
1403+ # TODO: Unfortunately, to be able to use ubuntuone.platform.credentials on
1404+ # linux, we now depend on dbus. :(
1405 from dbus.mainloop.qt import DBusQtMainLoop
1406 main_loop = DBusQtMainLoop(set_as_default=True)
1407 app = QtGui.QApplication(sys.argv)

Subscribers

People subscribed via source and target branches