boartty/boartty/view/story.py

860 lines
34 KiB
Python

# Copyright 2014 OpenStack Foundation
# Copyright 2014 Hewlett-Packard Development Company, L.P.
# Copyright 2016 Red Hat, Inc
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import collections
import datetime
import logging
try:
import ordereddict
except:
pass
import textwrap
from six.moves.urllib import parse as urlparse
import urwid
from boartty import keymap
from boartty import mywid
from boartty import sync
from boartty.view import mouse_scroll_decorator
import boartty.view
try:
OrderedDict = collections.OrderedDict
except AttributeError:
OrderedDict = ordereddict.OrderedDict
class NewStoryDialog(urwid.WidgetWrap, mywid.LineBoxTitlePropertyMixin):
signals = ['save', 'cancel']
def __init__(self, app):
self.app = app
save_button = mywid.FixedButton(u'Save')
cancel_button = mywid.FixedButton(u'Cancel')
urwid.connect_signal(save_button, 'click',
lambda button:self._emit('save'))
urwid.connect_signal(cancel_button, 'click',
lambda button:self._emit('cancel'))
rows = []
buttons = [('pack', save_button),
('pack', cancel_button)]
buttons = urwid.Columns(buttons, dividechars=2)
self.project_button = ProjectButton(self.app)
self.title_field = mywid.MyEdit(u'', edit_text=u'', ring=app.ring)
self.description_field = mywid.MyEdit(u'', edit_text='',
multiline=True, ring=app.ring)
for (label, w) in [
(u'Title:', self.title_field),
(u'Description:', self.description_field),
(u'Project:', ('pack', self.project_button)),
]:
row = urwid.Columns([(12, urwid.Text(label)), w])
rows.append(row)
rows.append(urwid.Divider())
rows.append(buttons)
pile = urwid.Pile(rows)
fill = urwid.Filler(pile, valign='top')
super(NewStoryDialog, self).__init__(urwid.LineBox(fill, 'New Story'))
class ProjectButton(mywid.SearchSelectButton):
def __init__(self, app, key=None, value=None):
self.app = app
super(ProjectButton, self).__init__(app, 'Select Project', key, value,
self.getValues)
def getValues(self):
with self.app.db.getSession() as session:
projects = session.getProjects()
for project in projects:
yield (project.key, project.name)
class StatusButton(mywid.SearchSelectButton):
def __init__(self, app):
self.app = app
super(StatusButton, self).__init__(app, 'Select Status', 'todo', 'todo',
self.getValues)
def getValues(self):
return [('todo', 'todo'),
('merged', 'merged'),
('invalid', 'invalid'),
('review', 'review'),
('inprogress', 'inprogress'),
]
class AssigneeButton(mywid.SearchSelectButton):
def __init__(self, app):
self.app = app
super(AssigneeButton, self).__init__(app, 'Select Assignee', None, None,
self.getValues)
def getValues(self):
with self.app.db.getSession() as session:
users = session.getUsers()
for user in users:
if user.name is not None:
yield (user.key, user.name)
class NewTaskDialog(urwid.WidgetWrap, mywid.LineBoxTitlePropertyMixin):
signals = ['save', 'cancel']
def __init__(self, app):
self.app = app
save_button = mywid.FixedButton(u'Save')
cancel_button = mywid.FixedButton(u'Cancel')
urwid.connect_signal(save_button, 'click',
lambda button:self._emit('save'))
urwid.connect_signal(cancel_button, 'click',
lambda button:self._emit('cancel'))
rows = []
buttons = [('pack', save_button),
('pack', cancel_button)]
buttons = urwid.Columns(buttons, dividechars=2)
self.project_button = ProjectButton(self.app)
self.status_button = StatusButton(self.app)
self.assignee_button = AssigneeButton(self.app)
self.title_field = mywid.MyEdit(u'', edit_text=u'', ring=app.ring)
for (label, w) in [
(u'Project:', ('pack', self.project_button)),
(u'Title:', self.title_field),
(u'Status:', ('pack', self.status_button)),
(u'Assignee:', ('pack', self.assignee_button)),
]:
row = urwid.Columns([(12, urwid.Text(label)), w])
rows.append(row)
rows.append(urwid.Divider())
rows.append(buttons)
pile = urwid.Pile(rows)
fill = urwid.Filler(pile, valign='top')
super(NewTaskDialog, self).__init__(urwid.LineBox(fill, 'New Task'))
class TaskRow(urwid.WidgetWrap):
task_focus_map = {
'task-title': 'focused-task-title',
'task-project': 'focused-task-project',
'task-status': 'focused-task-status',
'task-assignee': 'focused-task-assignee',
'task-note': 'focused-task-note',
}
def keypress(self, size, key):
if not self.app.input_buffer:
key = super(TaskRow, self).keypress(size, key)
keys = self.app.input_buffer + [key]
commands = self.app.config.keymap.getCommands(keys)
if keymap.DELETE_TASK in commands:
self.delete()
return None
return key
def __init__(self, app, story_view, task):
super(TaskRow, self).__init__(urwid.Pile([]))
self.app = app
self.story_view = story_view
self.task_key = task.key
self._note = u''
self.taskid = mywid.TextButton(self._note)
urwid.connect_signal(self.taskid, 'click',
lambda b:self.editNote(b))
self.project = ProjectButton(self.app)
urwid.connect_signal(self.project, 'changed',
lambda b:self.updateProject(b))
self.status = StatusButton(self.app)
urwid.connect_signal(self.status, 'changed',
lambda b:self.updateStatus(b))
self._title = u''
self.title = mywid.TextButton(self._title)
urwid.connect_signal(self.title, 'click',
lambda b:self.editTitle(b))
self.assignee = AssigneeButton(self.app)
urwid.connect_signal(self.assignee, 'changed',
lambda b:self.updateAssignee(b))
self.description = urwid.Text(u'')
self.columns = urwid.Columns([], dividechars=1)
for (widget, attr, packing) in [
(self.taskid, 'task-id', ('given', 4, False)),
(self.project, 'task-project', ('weight', 1, False)),
(self.title, 'task-title', ('weight', 2, False)),
(self.status, 'task-status', ('weight', 1, False)),
(self.assignee, 'task-assignee', ('weight', 1, False)),
]:
w = urwid.AttrMap(urwid.Padding(widget, width='pack'), attr,
focus_map={'focused': 'focused-'+attr})
self.columns.contents.append((w, packing))
self.pile = urwid.Pile([self.columns])
self.note = urwid.Text(u'')
self.note_visible = False
self.note_columns = urwid.Columns([], dividechars=1)
self.note_columns.contents.append((urwid.Text(u''), ('given', 1, False)))
self.note_columns.contents.append((self.note, ('weight', 1, False)))
self._w = urwid.AttrMap(self.pile, None)#, focus_map=self.task_focus_map)
self.refresh(task)
def setNote(self, note):
if note:
self._note = note
self.note.set_text(('task-note', self._note))
if not self.note_visible:
self.pile.contents.append((self.note_columns, ('weight', 1)))
self.note_visible = True
elif self.note_visible:
for x in self.pile.contents[:]:
if x[0] is self.note_columns:
self.pile.contents.remove(x)
self.note_visible = False
def refresh(self, task):
self.taskid.text.set_text(str(task.id))
self.project.update(task.project.key, task.project.name)
self.status.update(task.status, task.status)
self._title = task.title
self.title.text.set_text(self._title)
self.setNote(task.link)
if task.assignee:
self.assignee.update(task.assignee.key, task.assignee.name)
else:
self.assignee.update(None, 'Unassigned')
def updateProject(self, project_button):
with self.app.db.getSession() as session:
task = session.getTask(self.task_key)
project = session.getProject(project_button.key)
task.project = project
self.app.sync.submitTask(
sync.UpdateTaskTask(task.key, sync.HIGH_PRIORITY))
def updateStatus(self, status_button):
with self.app.db.getSession() as session:
task = session.getTask(self.task_key)
task.status = status_button.key
self.app.sync.submitTask(
sync.UpdateTaskTask(task.key, sync.HIGH_PRIORITY))
def updateAssignee(self, assignee_button):
with self.app.db.getSession() as session:
task = session.getTask(self.task_key)
user = session.getUser(assignee_button.key)
task.assignee = user
self.app.sync.submitTask(
sync.UpdateTaskTask(task.key, sync.HIGH_PRIORITY))
def editTitle(self, title_button):
dialog = mywid.LineEditDialog(self.app, 'Edit Task Title', '',
'Title: ', self._title,
ring=self.app.ring)
urwid.connect_signal(dialog, 'save',
lambda button: self.updateTitle(dialog, True))
urwid.connect_signal(dialog, 'cancel',
lambda button: self.updateTitle(dialog, False))
self.app.popup(dialog)
def updateTitle(self, dialog, save):
if save:
with self.app.db.getSession() as session:
task = session.getTask(self.task_key)
task.title = dialog.entry.edit_text
self._title = task.title
self.title.text.set_text(self._title)
self.app.sync.submitTask(
sync.UpdateTaskTask(task.key, sync.HIGH_PRIORITY))
self.app.backScreen()
def editNote(self, note_button):
dialog = mywid.LineEditDialog(self.app, 'Edit Task Note', '',
'Note: ', self._note,
ring=self.app.ring)
urwid.connect_signal(dialog, 'save',
lambda button: self.updateNote(dialog, True))
urwid.connect_signal(dialog, 'cancel',
lambda button: self.updateNote(dialog, False))
self.app.popup(dialog)
def updateNote(self, dialog, save):
if save:
with self.app.db.getSession() as session:
task = session.getTask(self.task_key)
task.link = dialog.entry.edit_text or None
self.setNote(task.link)
self.app.sync.submitTask(
sync.UpdateTaskTask(task.key, sync.HIGH_PRIORITY))
self.app.backScreen()
def delete(self):
dialog = mywid.YesNoDialog(u'Delete Task',
u'Are you sure you want to delete this task?')
urwid.connect_signal(dialog, 'no', lambda d: self.app.backScreen())
urwid.connect_signal(dialog, 'yes', self.finishDelete)
self.app.popup(dialog)
def finishDelete(self, dialog):
with self.app.db.getSession() as session:
task = session.getTask(self.task_key)
task.pending_delete = True
self.app.sync.submitTask(
sync.UpdateTaskTask(task.key, sync.HIGH_PRIORITY))
self.app.backScreen()
self.story_view.refresh()
def search(self, search, attribute):
if self.title.search(search, attribute):
return True
return False
class StoryButton(urwid.Button):
button_left = urwid.Text(u' ')
button_right = urwid.Text(u' ')
def __init__(self, story_view, story_key, text):
super(StoryButton, self).__init__('')
self.set_label(text)
self.story_view = story_view
self.story_key = story_key
urwid.connect_signal(self, 'click',
lambda button: self.openStory())
def set_label(self, text):
super(StoryButton, self).set_label(text)
def openStory(self):
try:
self.story_view.app.changeScreen(StoryView(self.story_view.app, self.story_key))
except boartty.view.DisplayError as e:
self.story_view.app.error(e.message)
class StoryEventBox(mywid.HyperText):
def __init__(self, story_view, event):
super(StoryEventBox, self).__init__(u'')
self.story_view = story_view
self.app = story_view.app
self.refresh(event)
def formatReply(self):
text = self.comment_text
pgraphs = []
pgraph_accumulator = []
wrap = True
for line in text.split('\n'):
if line.startswith('> '):
wrap = False
line = '> ' + line
if not line:
if pgraph_accumulator:
pgraphs.append((wrap, '\n'.join(pgraph_accumulator)))
pgraph_accumulator = []
wrap = True
continue
pgraph_accumulator.append(line)
if pgraph_accumulator:
pgraphs.append((wrap, '\n'.join(pgraph_accumulator)))
pgraph_accumulator = []
wrap = True
wrapper = textwrap.TextWrapper(initial_indent='> ',
subsequent_indent='> ')
wrapped_pgraphs = []
for wrap, pgraph in pgraphs:
if wrap:
wrapped_pgraphs.append('\n'.join(wrapper.wrap(pgraph)))
else:
wrapped_pgraphs.append(pgraph)
return '\n>\n'.join(wrapped_pgraphs)
def reply(self):
reply_text = self.formatReply()
if reply_text:
reply_text = self.event_creator + ' wrote:\n\n' + reply_text + '\n'
self.story_view.leaveComment(reply_text=reply_text)
def refresh(self, event):
self.event_id = event.id
self.event_creator = event.creator_name
description = event.description
if event.comment:
comment = event.comment.content
else:
comment = ''
self.comment_text = comment
created = self.app.time(event.created)
lines = comment.split('\n')
if event.creator.id == self.app.user_id:
name_style = 'story-event-own-name'
header_style = 'story-event-own-header'
creator_string = event.creator.name
else:
name_style = 'story-event-name'
header_style = 'story-event-header'
if event.creator.email:
creator_string = "%s <%s>" % (
event.creator.name,
event.creator.email)
else:
creator_string = event.creator.name
text = [(name_style, creator_string),
(header_style, ': '+description),
(header_style,
created.strftime(' (%Y-%m-%d %H:%M:%S%z)'))]
if event.comment and event.comment.draft and not event.comment.pending:
text.append(('story-event-draft', ' (draft)'))
elif event.comment:
link = mywid.Link('< Reply >',
'story-event-button',
'focused-story-event-button')
urwid.connect_signal(link, 'selected',
lambda link:self.reply())
text.append(' ')
text.append(link)
text.append('\n')
if lines and lines[-1]:
lines.append('')
comment_text = ['\n'.join(lines)]
for commentlink in self.app.config.commentlinks:
comment_text = commentlink.run(self.app, comment_text)
info = event.info or ''
if info:
info = [info + '\n']
else:
info = []
self.set_text(text+comment_text+info)
def search(self, search, attribute):
if self.text.search(search, attribute):
return True
return False
class DescriptionBox(mywid.HyperText):
def __init__(self, app, description):
self.app = app
super(DescriptionBox, self).__init__(description)
def set_text(self, text):
text = [text]
for commentlink in self.app.config.commentlinks:
text = commentlink.run(self.app, text)
super(DescriptionBox, self).set_text(text)
@mouse_scroll_decorator.ScrollByWheel
class StoryView(urwid.WidgetWrap, mywid.Searchable):
def getCommands(self):
return [
(keymap.TOGGLE_HIDDEN,
"Toggle the hidden flag for the current story"),
(keymap.NEXT_STORY,
"Go to the next story in the list"),
(keymap.PREV_STORY,
"Go to the previous story in the list"),
(keymap.LEAVE_COMMENT,
"Leave a comment on the story"),
(keymap.NEW_TASK,
"Add a new task to the current story"),
(keymap.TOGGLE_HELD,
"Toggle the held flag for the current story"),
(keymap.TOGGLE_HIDDEN_COMMENTS,
"Toggle display of hidden comments"),
(keymap.SEARCH_RESULTS,
"Back to the list of stories"),
(keymap.TOGGLE_STARRED,
"Toggle the starred flag for the current story"),
(keymap.EDIT_DESCRIPTION,
"Edit the commit message of this story"),
(keymap.REFRESH,
"Refresh this story"),
(keymap.EDIT_TITLE,
"Edit the title of this story"),
(keymap.INTERACTIVE_SEARCH,
"Interactive search"),
]
def help(self):
key = self.app.config.keymap.formatKeys
commands = self.getCommands()
ret = [(c[0], key(c[0]), c[1]) for c in commands]
for k in self.app.config.reviewkeys.values():
action = ', '.join(['{category}:{value}'.format(**a) for a in k['approvals']])
ret.append(('', keymap.formatKey(k['key']), action))
return ret
def __init__(self, app, story_key):
super(StoryView, self).__init__(urwid.Pile([]))
self.log = logging.getLogger('boartty.view.story')
self.searchInit()
self.app = app
self.story_key = story_key
self.task_rows = {}
self.event_rows = {}
self.hide_events = True
self.marked_seen = False
self.title_label = urwid.Text(u'', wrap='clip')
self.creator_label = mywid.TextButton(u'', on_press=self.searchCreator)
self.tags_label = urwid.Text(u'', wrap='clip')
self.created_label = urwid.Text(u'', wrap='clip')
self.updated_label = urwid.Text(u'', wrap='clip')
self.status_label = urwid.Text(u'', wrap='clip')
self.permalink_label = mywid.TextButton(u'', on_press=self.openPermalink)
story_info = []
story_info_map={'story-data': 'focused-story-data'}
for l, v in [("Title", self.title_label),
("Creator", urwid.Padding(urwid.AttrMap(self.creator_label, None,
focus_map=story_info_map),
width='pack')),
("Tags", urwid.Padding(urwid.AttrMap(self.tags_label, None,
focus_map=story_info_map),
width='pack')),
("Created", self.created_label),
("Updated", self.updated_label),
("Status", self.status_label),
("Permalink", urwid.Padding(urwid.AttrMap(self.permalink_label, None,
focus_map=story_info_map),
width='pack')),
]:
row = urwid.Columns([(12, urwid.Text(('story-header', l), wrap='clip')), v])
story_info.append(row)
story_info = urwid.Pile(story_info)
self.description = DescriptionBox(app, u'')
self.listbox = urwid.ListBox(urwid.SimpleFocusListWalker([]))
self._w.contents.append((self.app.header, ('pack', 1)))
self._w.contents.append((urwid.Divider(), ('pack', 1)))
self._w.contents.append((self.listbox, ('weight', 1)))
self._w.set_focus(2)
self.listbox.body.append(story_info)
self.listbox.body.append(urwid.Divider())
self.listbox_tasks_start = len(self.listbox.body)
self.listbox.body.append(urwid.Divider())
self.listbox.body.append(self.description)
self.listbox.body.append(urwid.Divider())
self.refresh()
self.listbox.set_focus(3)
def interested(self, event):
if not ((isinstance(event, sync.StoryAddedEvent) and
self.story_key == event.story_key)
or
(isinstance(event, sync.StoryUpdatedEvent) and
self.story_key == event.story_key)):
self.log.debug("Ignoring refresh story due to event %s" % (event,))
return False
self.log.debug("Refreshing story due to event %s" % (event,))
return True
def refresh(self):
with self.app.db.getSession() as session:
story = session.getStory(self.story_key)
# When we first open the story, update its last_seen
# time.
if not self.marked_seen:
story.last_seen = datetime.datetime.utcnow()
self.marked_seen = True
hidden = starred = held = ''
# storyboard
#if story.hidden:
# hidden = ' (hidden)'
#if story.starred:
# starred = '* '
#if story.held:
# held = ' (held)'
self.title = '%sStory %s%s%s' % (starred, story.id,
hidden, held)
self.app.status.update(title=self.title)
self.story_rest_id = story.id
self.story_title = story.title
if story.creator:
self.creator_email = story.creator.email
else:
self.creator_email = None
if self.creator_email:
creator_string = '%s <%s>' % (story.creator_name,
story.creator.email)
else:
creator_string = story.creator_name
self.creator_label.text.set_text(('story-data', creator_string))
tags_string = ' '.join([t.name for t in story.tags])
self.tags_label.set_text(('story-data', tags_string))
self.title_label.set_text(('story-data', story.title))
self.created_label.set_text(('story-data', str(self.app.time(story.created))))
self.updated_label.set_text(('story-data', str(self.app.time(story.updated))))
self.status_label.set_text(('story-data', story.status))
self.permalink_url = '' # storyboard urlparse.urljoin(self.app.config.url, str(story.number))
self.permalink_label.text.set_text(('story-data', self.permalink_url))
self.description.set_text(story.description)
# The listbox has both tasks and events in it, so
# keep track of the index separate from the loop.
listbox_index = self.listbox_tasks_start
# The set of task keys currently displayed
unseen_keys = set(self.task_rows.keys())
for task in story.tasks:
self.log.debug(task)
if task.pending_delete:
continue
row = self.task_rows.get(task.key)
if not row:
row = TaskRow(self.app, self, task)
self.listbox.body.insert(listbox_index, row)
self.task_rows[task.key] = row
else:
unseen_keys.remove(task.key)
row.refresh(task)
listbox_index += 1
# Remove any events that should not be displayed
for key in unseen_keys:
row = self.task_rows.get(key)
self.listbox.body.remove(row)
del self.task_rows[key]
listbox_index -= 1
listbox_index = len(self.listbox.body)
# Get the set of events that should be displayed
display_events = []
for event in story.events:
if event.comment or (not self.hide_events):
display_events.append(event)
# The set of event keys currently displayed
unseen_keys = set(self.event_rows.keys())
# Make sure all of the events that should be displayed are
for event in display_events:
row = self.event_rows.get(event.key)
if not row:
box = StoryEventBox(self, event)
row = urwid.Padding(box, width=80)
self.listbox.body.insert(listbox_index, row)
self.event_rows[event.key] = row
else:
unseen_keys.remove(event.key)
row.original_widget.refresh(event)
listbox_index += 1
# Remove any events that should not be displayed
for key in unseen_keys:
row = self.event_rows.get(key)
self.listbox.body.remove(row)
del self.event_rows[key]
listbox_index -= 1
def toggleHidden(self):
with self.app.db.getSession() as session:
story = session.getStory(self.story_key)
story.hidden = not story.hidden
self.app.project_cache.clear(story.project)
def toggleStarred(self):
with self.app.db.getSession() as session:
story = session.getStory(self.story_key)
story.starred = not story.starred
story.pending_starred = True
self.app.sync.submitTask(
sync.StoryStarredTask(self.story_key, sync.HIGH_PRIORITY))
def toggleHeld(self):
return self.app.toggleHeldStory(self.story_key)
def keypress(self, size, key):
if self.searchKeypress(size, key):
return None
if not self.app.input_buffer:
key = super(StoryView, self).keypress(size, key)
keys = self.app.input_buffer + [key]
commands = self.app.config.keymap.getCommands(keys)
if keymap.TOGGLE_HIDDEN in commands:
self.toggleHidden()
self.refresh()
return None
if keymap.TOGGLE_STARRED in commands:
self.toggleStarred()
self.refresh()
return None
if keymap.TOGGLE_HELD in commands:
self.toggleHeld()
self.refresh()
return None
if keymap.LEAVE_COMMENT in commands:
self.leaveComment()
return None
if keymap.NEW_TASK in commands:
self.newTask()
return None
if keymap.SEARCH_RESULTS in commands:
widget = self.app.findStoryList()
if widget:
self.app.backScreen(widget)
return None
if ((keymap.NEXT_STORY in commands) or
(keymap.PREV_STORY in commands)):
widget = self.app.findStoryList()
if widget:
if keymap.NEXT_STORY in commands:
new_story_key = widget.getNextStoryKey(self.story_key)
else:
new_story_key = widget.getPrevStoryKey(self.story_key)
if new_story_key:
try:
view = StoryView(self.app, new_story_key)
self.app.changeScreen(view, push=False)
except boartty.view.DisplayError as e:
self.app.error(e.message)
return None
if keymap.TOGGLE_HIDDEN_COMMENTS in commands:
self.hide_events = not self.hide_events
self.refresh()
return None
if keymap.EDIT_DESCRIPTION in commands:
self.editDescription()
return None
if keymap.REFRESH in commands:
self.app.sync.submitTask(
sync.SyncStoryTask(self.story_rest_id, priority=sync.HIGH_PRIORITY))
self.app.status.update()
return None
if keymap.EDIT_TITLE in commands:
self.editTitle()
return None
if keymap.INTERACTIVE_SEARCH in commands:
self.searchStart()
if keymap.FURTHER_INPUT not in commands:
self.app.clearInputBuffer()
return None
return key
def editDescription(self):
with self.app.db.getSession() as session:
story = session.getStory(self.story_key)
dialog = mywid.TextEditDialog(self.app, u'Edit Description',
u'Description:',
u'Save', story.description)
urwid.connect_signal(dialog, 'cancel', self.app.backScreen)
urwid.connect_signal(dialog, 'save', lambda button:
self.doEditDescription(dialog))
self.app.popup(dialog,
relative_width=50, relative_height=75,
min_width=60, min_height=20)
def doEditDescription(self, dialog):
with self.app.db.getSession() as session:
story = session.getStory(self.story_key)
story.description = dialog.entry.edit_text
story.pending = True
self.app.sync.submitTask(
sync.UpdateStoryTask(self.story_key, sync.HIGH_PRIORITY))
self.app.backScreen()
self.refresh()
def leaveComment(self, parent=None, reply_text=None):
with self.app.db.getSession() as session:
story = session.getStory(self.story_key)
event = story.getDraftCommentEvent(parent)
if event:
text = event.comment.content
else:
text = u''
if reply_text:
text += reply_text
dialog = mywid.TextEditDialog(self.app, u'Leave Comment', u'Comment:',
u'Save', text)
urwid.connect_signal(dialog, 'cancel', lambda button:
self.cancelLeaveComment(dialog, parent))
urwid.connect_signal(dialog, 'save', lambda button:
self.saveLeaveComment(dialog, parent))
self.app.popup(dialog,
relative_width=50, relative_height=75,
min_width=60, min_height=20)
def cancelLeaveComment(self, dialog, parent):
with self.app.db.getSession() as session:
story = session.getStory(self.story_key)
user = session.getUser(self.app.user_id)
story.setDraftComment(user, parent, dialog.entry.edit_text)
self.app.backScreen()
self.refresh()
def saveLeaveComment(self, dialog, parent):
with self.app.db.getSession() as session:
story = session.getStory(self.story_key)
user = session.getUser(self.app.user_id)
event = story.setDraftComment(user, parent, dialog.entry.edit_text)
event.comment.pending = True
self.app.sync.submitTask(
sync.AddCommentTask(event.key, sync.HIGH_PRIORITY))
self.app.backScreen()
self.refresh()
def newTask(self):
dialog = NewTaskDialog(self.app)
urwid.connect_signal(dialog, 'save',
lambda button: self.saveNewTask(dialog))
urwid.connect_signal(dialog, 'cancel',
lambda button: self.cancelNewTask(dialog))
self.app.popup(dialog,
relative_width=50, relative_height=25,
min_width=60, min_height=8)
def cancelNewTask(self, dialog):
self.app.backScreen()
def saveNewTask(self, dialog):
with self.app.db.getSession() as session:
story = session.getStory(self.story_key)
task = story.addTask()
task.project = session.getProjectByID(dialog.project_button.key)
task.title = dialog.title_field.edit_text
task.status = dialog.status_button.key
if dialog.assignee_button.key:
task.assignee = session.getUserByID(dialog.assignee_button.key)
task.pending = True
self.app.sync.submitTask(
sync.UpdateTaskTask(task.key, sync.HIGH_PRIORITY))
self.app.backScreen()
self.refresh()
def editTitle(self):
dialog = mywid.LineEditDialog(self.app, 'Edit Story Title', '',
'Title: ', self.story_title,
ring=self.app.ring)
urwid.connect_signal(dialog, 'save',
lambda button: self.updateTitle(dialog, True))
urwid.connect_signal(dialog, 'cancel',
lambda button: self.updateTitle(dialog, False))
self.app.popup(dialog)
def updateTitle(self, dialog, save):
if save:
with self.app.db.getSession() as session:
story = session.getStory(self.story_key)
story.title = dialog.entry.edit_text
self.app.sync.submitTask(
sync.UpdateStoryTask(story.key, sync.HIGH_PRIORITY))
self.app.backScreen()
self.refresh()
def openPermalink(self, widget):
self.app.openURL(self.permalink_url)
def searchCreator(self, widget):
if self.creator_email:
self.app.doSearch("status:open creator:%s" % (self.creator_email,))
def searchTags(self, widget):
#storyboard
if self.topic:
self.app.doSearch("status:open topic:%s" % (self.topic,))