コード例 #1
0
ファイル: launchbox.py プロジェクト: edwardt/hotchpotch
	def __init__(self):
		sysDoc = Connector().enum().sysStore()
		sysRev = Connector().lookup_doc(sysDoc).rev(sysDoc)
		with Connector().peek(sysRev) as r:
			root = struct.loads(r.readAll('HPSD'))
			self.syncDoc = root["syncrules"].doc()
		self.syncRev = Connector().lookup_doc(self.syncDoc).rev(sysDoc)
		with Connector().peek(self.syncRev) as r:
			self.rules = struct.loads(r.readAll('HPSD'))
コード例 #2
0
ファイル: container.py プロジェクト: edwardt/hotchpotch
	def docMergePerform(self, writer, baseReader, mergeReaders, changedParts):
		conflicts = super(CollectionWidget, self).docMergePerform(writer, baseReader, mergeReaders, changedParts)
		if 'HPSD' in changedParts:
			baseHpsd = struct.loads(baseReader.readAll('HPSD'))
			mergeHpsd = []
			for r in mergeReaders:
				mergeHpsd.append(struct.loads(r.readAll('HPSD')))
			(newHpsd, newConflict) = struct.merge(baseHpsd, mergeHpsd)
			conflicts = conflicts or newConflict
			writer.writeAll('HPSD', struct.dumps(newHpsd))

		return conflicts
コード例 #3
0
ファイル: container.py プロジェクト: edwardt/hotchpotch
    def docMergePerform(self, writer, baseReader, mergeReaders, changedParts):
        conflicts = super(CollectionWidget,
                          self).docMergePerform(writer, baseReader,
                                                mergeReaders, changedParts)
        if 'HPSD' in changedParts:
            baseHpsd = struct.loads(baseReader.readAll('HPSD'))
            mergeHpsd = []
            for r in mergeReaders:
                mergeHpsd.append(struct.loads(r.readAll('HPSD')))
            (newHpsd, newConflict) = struct.merge(baseHpsd, mergeHpsd)
            conflicts = conflicts or newConflict
            writer.writeAll('HPSD', struct.dumps(newHpsd))

        return conflicts
コード例 #4
0
ファイル: tests.py プロジェクト: edwardt/hotchpotch
	def test_sync_merge(self):
		(doc, rev1, rev2) = self.createMerge("org.hotchpotch.dict",
			{'HPSD' : struct.dumps({"a":1}) },
			{'HPSD' : struct.dumps({"a":1, "b":2}) },
			{'HPSD' : struct.dumps({"a":1, "c":3}) })
		l = self.performSync(doc, 'merge')

		rev = l.revs()[0]
		s = Connector().stat(rev)
		self.assertEqual(len(s.parents()), 2)
		self.assertTrue(rev1 in s.parents())
		self.assertTrue(rev2 in s.parents())

		# all revs on all stores?
		l = Connector().lookup_rev(rev1)
		self.assertTrue(self.store1 in l)
		self.assertTrue(self.store2 in l)
		l = Connector().lookup_rev(rev2)
		self.assertTrue(self.store1 in l)
		self.assertTrue(self.store2 in l)

		# see if merge was ok
		with Connector().peek(rev) as r:
			hpsd = struct.loads(r.readAll('HPSD'))
			self.assertEqual(hpsd, {"a":1, "b":2, "c":3})
コード例 #5
0
ファイル: container.py プロジェクト: edwardt/hotchpotch
	def __addReplicateActions(self, menu, link):
		c = Connector()
		try:
			allVolumes = set(c.lookup_rev(self.rev()))
			if isinstance(link, struct.DocLink):
				lookup = c.lookup_doc(link.doc())
				curVolumes = set(lookup.stores())
				try:
					for rev in lookup.revs():
						curVolumes = curVolumes & set(c.lookup_rev(rev, curVolumes))
				except IOError:
					curVolumes = set()
			else:
				curVolumes = set(c.lookup_rev(link.rev()))
		except IOError:
			return
		repVolumes = allVolumes - curVolumes
		for store in repVolumes:
			try:
				rev = c.lookup_doc(store).rev(store)
				with c.peek(rev) as r:
					metaData = struct.loads(r.readAll('META'))
					try:
						name = metaData["org.hotchpotch.annotation"]["title"]
					except:
						name = "Unknown store"
					action = menu.addAction("Replicate item to '%s'" % name)
					action.triggered.connect(
						lambda x,l=link,s=store: self.__doReplicate(l, s))
			except:
				pass
コード例 #6
0
ファイル: container.py プロジェクト: edwardt/hotchpotch
 def __addReplicateActions(self, menu, link):
     c = Connector()
     try:
         allVolumes = set(c.lookup_rev(self.rev()))
         if isinstance(link, struct.DocLink):
             lookup = c.lookup_doc(link.doc())
             curVolumes = set(lookup.stores())
             try:
                 for rev in lookup.revs():
                     curVolumes = curVolumes & set(
                         c.lookup_rev(rev, curVolumes))
             except IOError:
                 curVolumes = set()
         else:
             curVolumes = set(c.lookup_rev(link.rev()))
     except IOError:
         return
     repVolumes = allVolumes - curVolumes
     for store in repVolumes:
         try:
             rev = c.lookup_doc(store).rev(store)
             with c.peek(rev) as r:
                 metaData = struct.loads(r.readAll('META'))
                 try:
                     name = metaData["org.hotchpotch.annotation"]["title"]
                 except:
                     name = "Unknown store"
                 action = menu.addAction("Replicate item to '%s'" % name)
                 action.triggered.connect(
                     lambda x, l=link, s=store: self.__doReplicate(l, s))
         except:
             pass
コード例 #7
0
ファイル: tests.py プロジェクト: edwardt/hotchpotch
    def test_sync_merge(self):
        (doc, rev1,
         rev2) = self.createMerge("org.hotchpotch.dict",
                                  {'HPSD': struct.dumps({"a": 1})},
                                  {'HPSD': struct.dumps({
                                      "a": 1,
                                      "b": 2
                                  })},
                                  {'HPSD': struct.dumps({
                                      "a": 1,
                                      "c": 3
                                  })})
        l = self.performSync(doc, 'merge')

        rev = l.revs()[0]
        s = Connector().stat(rev)
        self.assertEqual(len(s.parents()), 2)
        self.assertTrue(rev1 in s.parents())
        self.assertTrue(rev2 in s.parents())

        # all revs on all stores?
        l = Connector().lookup_rev(rev1)
        self.assertTrue(self.store1 in l)
        self.assertTrue(self.store2 in l)
        l = Connector().lookup_rev(rev2)
        self.assertTrue(self.store1 in l)
        self.assertTrue(self.store2 in l)

        # see if merge was ok
        with Connector().peek(rev) as r:
            hpsd = struct.loads(r.readAll('HPSD'))
            self.assertEqual(hpsd, {"a": 1, "b": 2, "c": 3})
コード例 #8
0
ファイル: container.py プロジェクト: edwardt/hotchpotch
	def doLoad(self, handle, readWrite, autoClean):
		self.__mutable = readWrite
		self.__changedContent = False
		self.__autoClean = autoClean
		self.__typeCodes = set()
		self._listing = []
		if self.__linkMap:
			data = struct.loads(handle.readAll('HPSD'),
				lookup=lambda doc: self.__linkMap.lookup(doc))
		else:
			data = struct.loads(handle.readAll('HPSD'))
		listing = self.decode(data)
		for entry in listing:
			if entry.isValid() or (not self.__autoClean):
				self.__typeCodes.add(entry.getTypeCode())
				self._listing.append(entry)
				Connector().watch(entry)
			else:
				self.__changedContent = True
		self.reset()
コード例 #9
0
ファイル: container.py プロジェクト: edwardt/hotchpotch
 def doLoad(self, handle, readWrite, autoClean):
     self.__mutable = readWrite
     self.__changedContent = False
     self.__autoClean = autoClean
     self.__typeCodes = set()
     self._listing = []
     if self.__linkMap:
         data = struct.loads(handle.readAll('HPSD'),
                             lookup=lambda doc: self.__linkMap.lookup(doc))
     else:
         data = struct.loads(handle.readAll('HPSD'))
     listing = self.decode(data)
     for entry in listing:
         if entry.isValid() or (not self.__autoClean):
             self.__typeCodes.add(entry.getTypeCode())
             self._listing.append(entry)
             Connector().watch(entry)
         else:
             self.__changedContent = True
     self.reset()
コード例 #10
0
ファイル: launchbox.py プロジェクト: edwardt/hotchpotch
	def __syncToHotchpotch(self):
		# will also be triggered by _syncToFilesystem
		# apply hash to check if really changed from outside
		newFileHash = self._hashFile()
		if newFileHash != self.__fileHash:
			with open(self._path, "rb") as f:
				with Connector().update(self.__doc, self._rev) as w:
					meta = struct.loads(w.readAll('META'))
					if not "org.hotchpotch.annotation" in meta:
						meta["org.hotchpotch.annotation"] = {}
					meta["org.hotchpotch.annotation"]["comment"] = "<<Changed by external app>>"
					w.writeAll('META', struct.dumps(meta))
					w.writeAll('FILE', f.read())
					w.commit()
					self._rev = w.getRev()
					self.__fileHash = newFileHash
コード例 #11
0
ファイル: properties.py プロジェクト: edwardt/hotchpotch
 def __save(self):
     rev = self.revs[0]
     self.buttonBox.button(QtGui.QDialogButtonBox.Save).setEnabled(False)
     with Connector().peek(rev) as r:
         metaData = struct.loads(r.readAll("META"))
     setMetaData(metaData, ["org.hotchpotch.annotation", "title"], str(self.annoTab.titleEdit.text()))
     setMetaData(metaData, ["org.hotchpotch.annotation", "description"], str(self.annoTab.descEdit.text()))
     if self.annoTab.tagsEdit.hasAcceptableInput():
         tagString = self.annoTab.tagsEdit.text()
         tagSet = set([tag.strip() for tag in str(tagString).split(",")])
         tagList = list(tagSet)
         setMetaData(metaData, ["org.hotchpotch.annotation", "tags"], tagList)
     with Connector().update(self.doc, rev) as writer:
         writer.writeAll("META", struct.dumps(metaData))
         writer.commit()
         self.revs[0] = writer.getRev()
コード例 #12
0
ファイル: properties.py プロジェクト: edwardt/hotchpotch
    def __init__(self, revs, parent=None):
        super(HistoryTab, self).__init__(parent)

        # TODO: implement something nice gitk like...
        self.__historyList = []
        self.__historyRevs = []
        heads = revs[:]
        while len(heads) > 0:
            newHeads = []
            for rev in heads:
                try:
                    if rev not in self.__historyRevs:
                        stat = Connector().stat(rev)
                        mtime = str(stat.mtime())
                        comment = ""
                        if 'META' in stat.parts():
                            try:
                                with Connector().peek(rev) as r:
                                    metaData = struct.loads(r.readAll('META'))
                                    comment = extractMetaData(
                                        metaData, [
                                            "org.hotchpotch.annotation",
                                            "comment"
                                        ], "")
                            except IOError:
                                pass
                        self.__historyList.append(mtime + " - " + comment)
                        self.__historyRevs.append(rev)
                        newHeads.extend(stat.parents())
                except IOError:
                    pass
            heads = newHeads

        self.__historyListBox = QtGui.QListWidget()
        self.__historyListBox.setSizePolicy(QtGui.QSizePolicy.Ignored,
                                            QtGui.QSizePolicy.Ignored)
        self.__historyListBox.insertItems(0, self.__historyList)
        QtCore.QObject.connect(
            self.__historyListBox,
            QtCore.SIGNAL("itemDoubleClicked(QListWidgetItem *)"), self.__open)

        layout = QtGui.QVBoxLayout()
        layout.addWidget(self.__historyListBox)
        self.setLayout(layout)
コード例 #13
0
ファイル: launchbox.py プロジェクト: edwardt/hotchpotch
	def _updateFileName(self, guid):
		try:
			with Connector().peek(self._rev) as r:
				meta = struct.loads(r.readAll('META'))
		except IOError:
			meta = {}

		name = ''
		ext = ''
		if "org.hotchpotch.annotation" in meta:
			annotation = meta["org.hotchpotch.annotation"]
			if "title" in annotation:
				(name, ext) = os.path.splitext(annotation["title"])
		if name == '':
			if "origin" in annotation:
				name = os.path.splitext(annotation["origin"])[0]
		if name == '':
			name = guid.encode('hex')
		if ext == '':
			uti = Connector().stat(self._rev).type()
			extensions = Registry().search(uti, "extensions")
			if extensions:
				ext = extensions[0]
		if ext == '':
			if "origin" in annotation:
				ext  = os.path.splitext(annotation["origin"])[1]
		if ext == '':
			ext = '.bin'

		basePath = os.path.join(self._basePath, guid.encode('hex'))
		if not os.path.isdir(basePath):
			os.makedirs(basePath)
		newPath = os.path.join(basePath, name+ext)
		oldPath = self._path
		if oldPath != newPath:
			# try to rename the current file
			try:
				if oldPath and os.path.isfile(oldPath):
					os.rename(oldPath, newPath)
				self._path = newPath
			except OSError:
				# FIXME: inform user
				pass
コード例 #14
0
ファイル: properties.py プロジェクト: edwardt/hotchpotch
 def __save(self):
     rev = self.revs[0]
     self.buttonBox.button(QtGui.QDialogButtonBox.Save).setEnabled(False)
     with Connector().peek(rev) as r:
         metaData = struct.loads(r.readAll('META'))
     setMetaData(metaData, ["org.hotchpotch.annotation", "title"],
                 str(self.annoTab.titleEdit.text()))
     setMetaData(metaData, ["org.hotchpotch.annotation", "description"],
                 str(self.annoTab.descEdit.text()))
     if self.annoTab.tagsEdit.hasAcceptableInput():
         tagString = self.annoTab.tagsEdit.text()
         tagSet = set([tag.strip() for tag in str(tagString).split(',')])
         tagList = list(tagSet)
         setMetaData(metaData, ["org.hotchpotch.annotation", "tags"],
                     tagList)
     with Connector().update(self.doc, rev) as writer:
         writer.writeAll('META', struct.dumps(metaData))
         writer.commit()
         self.revs[0] = writer.getRev()
コード例 #15
0
ファイル: properties.py プロジェクト: edwardt/hotchpotch
    def __init__(self, revs, parent=None):
        super(HistoryTab, self).__init__(parent)

        # TODO: implement something nice gitk like...
        self.__historyList = []
        self.__historyRevs = []
        heads = revs[:]
        while len(heads) > 0:
            newHeads = []
            for rev in heads:
                try:
                    if rev not in self.__historyRevs:
                        stat = Connector().stat(rev)
                        mtime = str(stat.mtime())
                        comment = ""
                        if "META" in stat.parts():
                            try:
                                with Connector().peek(rev) as r:
                                    metaData = struct.loads(r.readAll("META"))
                                    comment = extractMetaData(metaData, ["org.hotchpotch.annotation", "comment"], "")
                            except IOError:
                                pass
                        self.__historyList.append(mtime + " - " + comment)
                        self.__historyRevs.append(rev)
                        newHeads.extend(stat.parents())
                except IOError:
                    pass
            heads = newHeads

        self.__historyListBox = QtGui.QListWidget()
        self.__historyListBox.setSizePolicy(QtGui.QSizePolicy.Ignored, QtGui.QSizePolicy.Ignored)
        self.__historyListBox.insertItems(0, self.__historyList)
        QtCore.QObject.connect(
            self.__historyListBox, QtCore.SIGNAL("itemDoubleClicked(QListWidgetItem *)"), self.__open
        )

        layout = QtGui.QVBoxLayout()
        layout.addWidget(self.__historyListBox)
        self.setLayout(layout)
コード例 #16
0
ファイル: container.py プロジェクト: edwardt/hotchpotch
	def __updateColumns(self):
		# This makes only sense if we're a valid entry
		if not self.__valid:
			return

		try:
			stat = Connector().stat(self.__rev)
			with Connector().peek(self.__rev) as r:
				try:
					metaData = struct.loads(r.readAll('META'))
				except:
					metaData = { }

			for i in xrange(len(self.__columnDefs)):
				column = self.__columnDefs[i]
				if column.derived():
					self.__columnValues[i] = column.extract(stat, metaData)

		except IOError:
			for i in xrange(len(self.__columnDefs)):
				column = self.__columnDefs[i]
				if column.derived():
					self.__columnValues[i] = column.default()
コード例 #17
0
ファイル: container.py プロジェクト: edwardt/hotchpotch
    def __updateColumns(self):
        # This makes only sense if we're a valid entry
        if not self.__valid:
            return

        try:
            stat = Connector().stat(self.__rev)
            with Connector().peek(self.__rev) as r:
                try:
                    metaData = struct.loads(r.readAll('META'))
                except:
                    metaData = {}

            for i in xrange(len(self.__columnDefs)):
                column = self.__columnDefs[i]
                if column.derived():
                    self.__columnValues[i] = column.extract(stat, metaData)

        except IOError:
            for i in xrange(len(self.__columnDefs)):
                column = self.__columnDefs[i]
                if column.derived():
                    self.__columnValues[i] = column.default()
コード例 #18
0
ファイル: hp-enum.py プロジェクト: edwardt/hotchpotch
	state = ""
	if enum.isMounted(store):
		state += 'M'
	else:
		state += '-'
	if enum.isSystem(store):
		state += 'S'
	else:
		state += '-'
	if enum.isRemovable(store):
		state += 'R'
	else:
		state += '-'
	if enum.isNet(store):
		state += 'N'
	else:
		state += '-'

	if enum.isMounted(store):
		doc = enum.doc(store)
		try:
			rev = Connector().lookup_doc(doc).rev(doc)
			with Connector().peek(rev) as r:
				metaData = struct.loads(r.readAll('META'))
				realName = metaData["org.hotchpotch.annotation"]["title"]
		except:
			realName = "unknwown"
		print "%s  %s %s  %s [%s]" % (state, store.ljust(8), doc.encode("hex"), realName, mountName)
	else:
		print "%s  %s [%s]" % (state, store.ljust(8), mountName)
コード例 #19
0
ファイル: hp-mount.py プロジェクト: edwardt/hotchpotch
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

import sys
from hotchpotch import Connector, struct

if len(sys.argv) == 2:
    if Connector().mount(sys.argv[1]):
        doc = Connector().enum().doc(sys.argv[1])
        try:
            rev = Connector().lookup_doc(doc).rev(doc)
            with Connector().peek(rev) as r:
                metaData = struct.loads(r.readAll("META"))
                name = metaData["org.hotchpotch.annotation"]["title"]
        except:
            name = "Unnamed store"
        print "Mounted '%s'" % name
    else:
        print "Mount failed"
        sys.exit(2)
else:
    print "Usage: hp-mount.py <id>"
    sys.exit(1)
コード例 #20
0
ファイル: properties.py プロジェクト: edwardt/hotchpotch
    def __init__(self, revs, edit, parent=None):
        super(AnnotationTab, self).__init__(parent)

        titles = set()
        self.__title = ""
        descriptions = set()
        description = ""
        tagSets = set()
        tags = []
        for rev in revs:
            try:
                with Connector().peek(rev) as r:
                    metaData = struct.loads(r.readAll('META'))
                    self.__title = extractMetaData(
                        metaData, ["org.hotchpotch.annotation", "title"], "")
                    titles.add(self.__title)
                    description = extractMetaData(
                        metaData, ["org.hotchpotch.annotation", "description"],
                        "")
                    descriptions.add(description)
                    tags = extractMetaData(
                        metaData, ["org.hotchpotch.annotation", "tags"], [])
                    tagSets.add(frozenset(tags))
            except IOError:
                pass

        layout = QtGui.QGridLayout()

        layout.addWidget(QtGui.QLabel("Title:"), 0, 0)
        if len(titles) > 1:
            layout.addWidget(QtGui.QLabel("<ambiguous>"), 0, 1)
        else:
            if edit:
                self.titleEdit = QtGui.QLineEdit()
                self.titleEdit.setText(self.__title)
                QtCore.QObject.connect(
                    self.titleEdit,
                    QtCore.SIGNAL("textEdited(const QString&)"),
                    self.__changed)
                layout.addWidget(self.titleEdit, 0, 1)
            else:
                layout.addWidget(QtGui.QLabel(self.__title), 0, 1)

        layout.addWidget(QtGui.QLabel("Description:"), 1, 0)
        if len(descriptions) > 1:
            layout.addWidget(QtGui.QLabel("<ambiguous>"), 1, 1)
        else:
            if edit:
                self.descEdit = QtGui.QLineEdit()
                self.descEdit.setText(description)
                QtCore.QObject.connect(
                    self.descEdit, QtCore.SIGNAL("textEdited(const QString&)"),
                    self.__changed)
                layout.addWidget(self.descEdit, 1, 1)
            else:
                descLabel = QtGui.QLabel(description)
                descLabel.setWordWrap(True)
                descLabel.setScaledContents(True)
                layout.addWidget(descLabel, 1, 1)

        layout.addWidget(QtGui.QLabel("Tags:"), 2, 0)
        if len(tagSets) > 1:
            layout.addWidget(QtGui.QLabel("<ambiguous>"), 2, 1)
        else:
            tags.sort()
            tagList = ""
            for tag in tags:
                tagList += ", " + tag
            if edit:
                self.tagsEdit = QtGui.QLineEdit()
                self.tagsEdit.setText(tagList[2:])
                self.tagsEdit.setValidator(
                    QtGui.QRegExpValidator(
                        QtCore.QRegExp("(\\s*\\w+\\s*(,\\s*\\w+\\s*)*)?"),
                        self))
                QtCore.QObject.connect(
                    self.tagsEdit, QtCore.SIGNAL("textEdited(const QString&)"),
                    self.__changed)
                layout.addWidget(self.tagsEdit, 2, 1)
            else:
                layout.addWidget(QtGui.QLabel(tagList[2:]), 2, 1)

        self.setLayout(layout)
コード例 #21
0
ファイル: properties.py プロジェクト: edwardt/hotchpotch
    def __init__(self, revs, edit, parent=None):
        super(AnnotationTab, self).__init__(parent)

        titles = set()
        self.__title = ""
        descriptions = set()
        description = ""
        tagSets = set()
        tags = []
        for rev in revs:
            try:
                with Connector().peek(rev) as r:
                    metaData = struct.loads(r.readAll("META"))
                    self.__title = extractMetaData(metaData, ["org.hotchpotch.annotation", "title"], "")
                    titles.add(self.__title)
                    description = extractMetaData(metaData, ["org.hotchpotch.annotation", "description"], "")
                    descriptions.add(description)
                    tags = extractMetaData(metaData, ["org.hotchpotch.annotation", "tags"], [])
                    tagSets.add(frozenset(tags))
            except IOError:
                pass

        layout = QtGui.QGridLayout()

        layout.addWidget(QtGui.QLabel("Title:"), 0, 0)
        if len(titles) > 1:
            layout.addWidget(QtGui.QLabel("<ambiguous>"), 0, 1)
        else:
            if edit:
                self.titleEdit = QtGui.QLineEdit()
                self.titleEdit.setText(self.__title)
                QtCore.QObject.connect(self.titleEdit, QtCore.SIGNAL("textEdited(const QString&)"), self.__changed)
                layout.addWidget(self.titleEdit, 0, 1)
            else:
                layout.addWidget(QtGui.QLabel(self.__title), 0, 1)

        layout.addWidget(QtGui.QLabel("Description:"), 1, 0)
        if len(descriptions) > 1:
            layout.addWidget(QtGui.QLabel("<ambiguous>"), 1, 1)
        else:
            if edit:
                self.descEdit = QtGui.QLineEdit()
                self.descEdit.setText(description)
                QtCore.QObject.connect(self.descEdit, QtCore.SIGNAL("textEdited(const QString&)"), self.__changed)
                layout.addWidget(self.descEdit, 1, 1)
            else:
                descLabel = QtGui.QLabel(description)
                descLabel.setWordWrap(True)
                descLabel.setScaledContents(True)
                layout.addWidget(descLabel, 1, 1)

        layout.addWidget(QtGui.QLabel("Tags:"), 2, 0)
        if len(tagSets) > 1:
            layout.addWidget(QtGui.QLabel("<ambiguous>"), 2, 1)
        else:
            tags.sort()
            tagList = ""
            for tag in tags:
                tagList += ", " + tag
            if edit:
                self.tagsEdit = QtGui.QLineEdit()
                self.tagsEdit.setText(tagList[2:])
                self.tagsEdit.setValidator(
                    QtGui.QRegExpValidator(QtCore.QRegExp("(\\s*\\w+\\s*(,\\s*\\w+\\s*)*)?"), self)
                )
                QtCore.QObject.connect(self.tagsEdit, QtCore.SIGNAL("textEdited(const QString&)"), self.__changed)
                layout.addWidget(self.tagsEdit, 2, 1)
            else:
                layout.addWidget(QtGui.QLabel(tagList[2:]), 2, 1)

        self.setLayout(layout)