Esempio n. 1
0
	def __init__(self, parent):
		super(ManageTopicsFrame, self).__init__(parent)
		attach_unknown_control("topic_tree", lambda parent: TopicTree(self, parent), self)
		attach_unknown_control("topic_selector", TopicSelector, self)
		attach_unknown_control("passage_list_ctrl", lambda parent: PassageListCtrl(self, parent), self)
		self.SetIcons(guiconfig.icons)
		self._manager = get_primary_passage_list_manager()
		self._operations_context = OperationsContext(self)
		self._operations_manager = ManageTopicsOperations(
				passage_list_manager=self._manager,
				context=self._operations_context
			)
		self._operations_manager.undo_available_changed_observers \
				+= self._undo_available_changed
		self._operations_manager.paste_available_changed_observers \
				+= self._paste_available_changed
		self._paste_available_changed()
		self._undo_available_changed()
		self._selected_topic = None
		# The topic that currently has passages displayed in the passage list
		# control.
		self._passage_list_topic = None
		self.is_passage_selected = False
		self.selected_passages = []
		self.topic_selector.topic_changed_observers.add_observer(self._set_selected_topic)
		self._setup_item_details_panel()
		self._init_passage_list_ctrl_headers()
		self._setup_passage_list_ctrl()
		self._setup_topic_tree()
		self._bind_events()
		self.Size = (725, 590)
		self.passage_list_splitter.SashGravity = 1.0
		wx.CallAfter(self.passage_list_splitter.SetSashPosition, 340)
		self._set_selected_topic(self._manager)
Esempio n. 2
0
class ManageTopicsFrame(xrcManageTopicsFrame):
	def __init__(self, parent):
		super(ManageTopicsFrame, self).__init__(parent)
		attach_unknown_control("topic_tree", lambda parent: TopicTree(self, parent), self)
		attach_unknown_control("topic_selector", TopicSelector, self)
		attach_unknown_control("passage_list_ctrl", lambda parent: PassageListCtrl(self, parent), self)
		self.SetIcons(guiconfig.icons)
		self._manager = get_primary_passage_list_manager()
		self._operations_context = OperationsContext(self)
		self._operations_manager = ManageTopicsOperations(
				passage_list_manager=self._manager,
				context=self._operations_context
			)
		self._operations_manager.undo_available_changed_observers \
				+= self._undo_available_changed
		self._operations_manager.paste_available_changed_observers \
				+= self._paste_available_changed
		self._paste_available_changed()
		self._undo_available_changed()
		self._selected_topic = None
		# The topic that currently has passages displayed in the passage list
		# control.
		self._passage_list_topic = None
		self.is_passage_selected = False
		self.selected_passages = []
		self.topic_selector.topic_changed_observers.add_observer(self._set_selected_topic)
		self._setup_item_details_panel()
		self._init_passage_list_ctrl_headers()
		self._setup_passage_list_ctrl()
		self._setup_topic_tree()
		self._bind_events()
		self.Size = (725, 590)
		self.passage_list_splitter.SashGravity = 1.0
		wx.CallAfter(self.passage_list_splitter.SetSashPosition, 340)
		self._set_selected_topic(self._manager)

	def _bind_events(self):
		self.Bind(wx.EVT_CLOSE, self._on_close)
		guiutil.add_close_window_esc_accelerator(self, self.Close)

		self.topic_tree.on_selection_changed += self._selected_topic_changed
		self.topic_tree.Bind(wx.EVT_TREE_ITEM_GETTOOLTIP, self._get_topic_tool_tip)
		self.topic_tree.Bind(wx.EVT_TREE_END_LABEL_EDIT, self._end_topic_label_edit)
		self.topic_tree.Bind(wx.EVT_TREE_BEGIN_LABEL_EDIT, self._begin_topic_label_edit)
		
		self.topic_tree.Bind(wx.EVT_TREE_ITEM_MENU, self._show_topic_context_menu)
		self.passage_list_ctrl.Bind(wx.EVT_LIST_ITEM_SELECTED, self._passage_selection_changed)
		self.passage_list_ctrl.Bind(wx.EVT_LIST_ITEM_DESELECTED, self._passage_selection_changed)
		self.passage_list_ctrl.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self._passage_activated)
		self.passage_list_ctrl.Bind(wx.EVT_LIST_ITEM_RIGHT_CLICK, self._show_passage_context_menu)

		# Trap the events with the topic tree and the passage list when they
		# get focus, so that we can know which one last got focus for our
		# copy and paste operations.
		self.topic_tree.Bind(wx.EVT_SET_FOCUS, self._topic_tree_got_focus)
		self.passage_list_ctrl.Bind(wx.EVT_SET_FOCUS, self._passage_list_got_focus)

		self.passage_list_ctrl.Bind(wx.EVT_KEY_UP, self._on_char)
		self.topic_tree.Bind(wx.EVT_KEY_UP, self._on_char)

		for tool in ("add_topic_tool", "add_passage_tool", "cut_tool", 
			"copy_tool", "copy_text_tool", "paste_tool", "delete_tool",
			"undo_tool", "redo_tool", "sort_order_toggle_tool"):
			handler = lambda event, tool=tool: self._perform_toolbar_action(event, tool)
			self.toolbar.Bind(wx.EVT_TOOL, handler, id=wx.xrc.XRCID(tool))

	def _setup_topic_tree(self):
		root = self.topic_tree.AddRoot(_("Topics"))
		self.topic_tree.SetPyData(root, self._manager)
		self._add_sub_topics(self._manager, root)
		self.topic_tree.Expand(root)

	def select_topic_and_passage(self, topic, passage_entry):
		"""Selects the given topic in the tree, and the given passage entry
		in the passage list.

		This allows the correct topic and passage to be displayed when a tag
		is clicked on.

		This assumes that the passage entry is one of the passages in the
		topic.
		"""
		self._set_selected_topic(topic)
		if passage_entry not in topic.passages:
			return
		index = topic.passages.index(passage_entry)
		self._select_list_entry_by_index(index)
		self.passage_list_ctrl.SetFocus()
		wx.CallAfter(lambda: self.passage_list_ctrl.EnsureVisible(index))

	def _get_tree_selected_topic(self):
		selection = self.topic_tree.GetSelection()
		if not selection.IsOk():
			return None
		return self.topic_tree.GetPyData(selection)
	
	def _set_selected_topic(self, topic):
		if topic is self.selected_topic:
			return

		tree_item = self._find_topic(self.topic_tree.GetRootItem(), topic)
		if tree_item is None:
			tree_item = self.topic_tree.GetRootItem()
		self.topic_tree.SelectItem(tree_item)
		self.topic_tree.EnsureVisible(tree_item)
		return tree_item

	def _selected_topic_changed(self, item):
		# Topic nodes are selected as they are dragged past, but we shouldn't
		# change the selected topic and passage list until the dragging has
		# been finished.
		if self.topic_tree._dragging:
			return

		selected_topic = self._get_tree_selected_topic()
		if selected_topic is None:
			return

		self.selected_topic = selected_topic
		self._setup_passage_list_ctrl()
		self._setup_order_passages_by()

		self.Title = self._get_title()

	def get_selected_topic(self):
		return self._selected_topic

	def set_selected_topic(self, new_topic):
		if new_topic is not None:
			new_topic.order_passages_by_observers += self._setup_order_passages_by
		if self._selected_topic is not None:
			self._selected_topic.order_passages_by_observers -= self._setup_order_passages_by
		self._selected_topic = new_topic
		self.selected_passages = []
		self._change_topic_details(new_topic)
		self.topic_selector.selected_topic = new_topic

	selected_topic = property(get_selected_topic, set_selected_topic)

	def _find_topic(self, tree_item, topic):
		if self.topic_tree.GetPyData(tree_item) is topic:
			return tree_item

		id, cookie = self.topic_tree.GetFirstChild(tree_item)
		while id.IsOk():
			node = self._find_topic(id, topic)
			if node is not None:
				return node
			id, cookie = self.topic_tree.GetNextChild(tree_item, cookie)

	def _get_title(self):
		"""Gets a title for the frame, based on the currently selected topic."""
		topic = self.selected_topic
		title = _("Manage Topics")
		if topic is not self._manager:
			title = "%s - %s" % (topic.full_name, title)
		return title

	def _setup_order_passages_by(self): 
		self.toolbar.ToggleTool(
			wx.xrc.XRCID("sort_order_toggle_tool"),
			bool(TOPIC_ORDER_BACKEND_OPTIONS.index(self.selected_topic.order_passages_by))
		)

	def _add_sub_topics(self, parent_list, parent_node):
		parent_list.add_subtopic_observers.add_observer(
				self._add_new_topic_node,
				(parent_node,))

		parent_list.remove_subtopic_observers.add_observer(
				self._remove_topic_node,
				(parent_node,))

		parent_list.name_changed_observers.add_observer(
				self._rename_topic_node,
				(parent_node,))

		for subtopic in parent_list.subtopics:
			self._add_topic_node(subtopic, parent_node)
	
	def _add_topic_node(self, passage_list, parent_node):
		if passage_list.is_special_topic:
			return

		node = self.topic_tree.AppendItem(parent_node, passage_list.name)
		self.topic_tree.SetPyData(node, passage_list)
		self._add_sub_topics(passage_list, node)
	
	def _add_new_topic_node(self, parent_node, topic):
		self._add_topic_node(topic, parent_node)

	def _remove_topic_node(self, parent_node, topic):
		topic_node = self._find_topic(parent_node, topic)
		self.topic_tree.Delete(topic_node)
		self._remove_observers(topic)

	def _rename_topic_node(self, parent_node, new_name):
		self.topic_tree.SetItemText(parent_node, new_name)
	
	def _get_topic_tool_tip(self, event):
		"""Gets the description for a topic.
		
		Note that this is Windows only, but it doesn't appear that there is
		any way for us to make our own tool tips without tracking the
		underlying window's mouse movements.
		"""
		event.SetToolTip(self.topic_tree.GetPyData(event.GetItem()).description)

	def _begin_topic_label_edit(self, event):
		"""This event is used to stop us editing the root node."""
		if event.GetItem() == self.topic_tree.RootItem:
			event.Veto()
	
	def _end_topic_label_edit(self, event):
		"""This event is used to update the names of topics.
		
		Any topic node can be edited, and its name will then be set based on
		the new label text.
		"""
		if not event.IsEditCancelled():
			topic = self.topic_tree.GetPyData(event.GetItem())
			self._operations_manager.set_topic_name(topic, event.GetLabel())

	def _on_char(self, event):
		"""Handles all keyboard shortcuts."""
		guiutil.dispatch_keypress(self._get_actions(), event)

	def _get_actions(self):
		"""Returns a list of actions to be used when handling keyboard
		shortcuts.
		"""
		actions = {
			(ord("C"), wx.MOD_CMD): self._operations_manager.copy,
			(ord("X"), wx.MOD_CMD): self._operations_manager.cut,
			(ord("V"), wx.MOD_CMD): self._safe_paste,
			wx.WXK_DELETE: self._delete,
			(ord("Z"), wx.MOD_CMD): self._operations_manager.undo,
			(ord("Y"), wx.MOD_CMD): self._operations_manager.redo,
			(ord("A"), wx.MOD_CMD): self._select_all_passages,
		}

		if not self._operations_manager.can_undo:
			del actions[(ord("Z"), wx.MOD_CMD)]
		if not self._operations_manager.can_redo:
			del actions[(ord("Y"), wx.MOD_CMD)]

		return actions

	def _perform_toolbar_action(self, event, tool_id):
		"""Performs the action requested from the toolbar."""
		event.Skip()
		if self.selected_topic is None:
			return

		actions = {
			"add_topic_tool": lambda: self._create_topic(self.selected_topic),
			"add_passage_tool": 
				lambda: self._create_passage(self.selected_topic),			
			"copy_tool":	self._operations_manager.copy,
			"copy_text_tool":	self._copy_as_text,
			"cut_tool":		self._operations_manager.cut,
			"paste_tool":	self._safe_paste,
			"delete_tool":	self._delete,
			"undo_tool":	self._operations_manager.undo,
			"redo_tool":	self._operations_manager.redo,
			"sort_order_toggle_tool":	self._sort_order_toggled,
		}
		actions[tool_id]()

	def _undo_available_changed(self):
		"""Enables or disables the undo and redo toolbar buttons,
		based on whether these actions are available.
		"""
		self.toolbar.EnableTool(wx.xrc.XRCID("undo_tool"),
				self._operations_manager.can_undo)
		self.toolbar.EnableTool(wx.xrc.XRCID("redo_tool"),
				self._operations_manager.can_redo)

	def _paste_available_changed(self):
		"""Enables or disables the paste toolbar button."""
		self.toolbar.EnableTool(wx.xrc.XRCID("paste_tool"),
				self._operations_manager.can_paste)

	def _show_topic_context_menu(self, event):
		"""Shows the context menu for a topic in the topic tree."""
		if not event.Item:
			event.Skip()
			return

		self.selected_topic = self.topic_tree.GetPyData(event.Item)
		menu = wx.Menu()
		
		item = menu.Append(wx.ID_ANY, _("&New Topic"))
		self.Bind(wx.EVT_MENU,
				lambda e: self._create_topic(self.selected_topic),
				id=item.Id)
		
		item = menu.Append(wx.ID_ANY, _("Add &Passage"))
		self.Bind(wx.EVT_MENU,
				lambda e: self._create_passage(self.selected_topic),
				id=item.Id)

		menu.AppendSeparator()
		
		item = menu.Append(wx.ID_ANY, _("Cu&t"))
		self.Bind(wx.EVT_MENU,
				lambda e: self._operations_manager.cut(),
				id=item.Id)

		item = menu.Append(wx.ID_ANY, _("&Copy"))
		self.Bind(wx.EVT_MENU,
				lambda e: self._operations_manager.copy(),
				id=item.Id)

		item = menu.Append(wx.ID_ANY, _("&Paste"))
		self.Bind(wx.EVT_MENU,
				lambda e: self._safe_paste(),
				id=item.Id)

		menu.AppendSeparator()
		
		item = menu.Append(wx.ID_ANY, _("Delete &Topic"))
		self.Bind(wx.EVT_MENU,
				lambda e: self._delete(),
				id=item.Id)
		
		self.topic_tree.PopupMenu(menu)
		menu.Destroy()

	def _copy_as_text(self):
		guiconfig.mainfrm.copy(self._get_current_topic_text())

	def _get_current_topic_text(self):
		if self.selected_topic is None:
			return u""
		text = self.selected_topic.full_name
		if self.selected_topic.description:
			text += u"\n" + self.selected_topic.description.strip()

		passage_text = u"\n".join(self._passage_entry_text(passage_entry)
				for passage_entry in self.selected_topic.passages)
		if passage_text:
			text += u"\n\n" + passage_text
	
		return text.strip()

	def _passage_entry_text(self, passage_entry):
		"""Gets the text for the given passage entry with its comment."""
		text = passage_entry.passage.GetBestRange(userOutput=True)
		if passage_entry.comment:
			text = u"%s: %s" % (text, passage_entry.comment.strip())
		return text

	@guiutil.frozen
	def _safe_paste(self, operation=None):
		"""A wrapper around the operations manager paste operation that
		catches the CircularDataException and displays an error message.
		"""
		if operation is None:
			operation = self._operations_manager.paste
		try:
			operation()
		except CircularDataException:
			wx.MessageBox(_("Cannot copy the topic to one of its children."),
					_("Copy Topic"), wx.OK | wx.ICON_ERROR, self)

	@guiutil.frozen
	def _delete(self):
		"""Deletes the currently selected item while keeping the GUI frozen."""
		if not self.is_passage_selected and self.selected_topic is self._manager:
			wx.MessageBox(_("Cannot delete the base topic."),
					_("Delete Topic"), wx.OK | wx.ICON_ERROR, self)
			return

		self._operations_manager.delete()
	
	@guiutil.frozen
	def _sort_order_toggled(self):
		is_ordered = self.toolbar.GetToolState(wx.xrc.XRCID("sort_order_toggle_tool"))
		order_passages_by = TOPIC_ORDER_BACKEND_OPTIONS[int(is_ordered)]
		self._operations_manager.set_order_passages_by(
				self.selected_topic,
				order_passages_by
			)
	
	def _create_topic(self, topic, creation_function=None):
		new_topic = self._operations_manager.add_new_topic(creation_function)
		self._set_selected_topic(new_topic)
		self.topic_details_panel.focus()
		self.topic_details_panel.combine_action = True

	def save_search_results(self, search_string, search_results):
		assert search_string
		self.topic_tree.SetFocus()
		name = _(u"Search: %s") % search_string
		description = _(u"Results from the search `%s'.") % search_string

		# Tags are not displayed by default for saved search results because
		# they are not really user created and it looks dubious having tags
		# "Search: My search" littering the screen.
		self._create_topic(self._manager,
				lambda: PassageList.create_from_verse_list(name, search_results, description, display_tag=False)
			)
	
	def _create_passage(self, topic):
		self._set_selected_topic(topic)
		passage = PassageEntry(None)
		self._change_passage_details([passage])
		self.passage_details_panel.begin_create_passage(topic, passage)
	
	def _on_close(self, event):
		self._remove_observers(self._manager)
		self._remove_passage_list_observers()
		self._manager.save()
		event.Skip()
	
	def _remove_observers(self, parent_topic):
		if parent_topic.is_special_topic:
			return

		parent_topic.add_subtopic_observers.remove(self._add_new_topic_node)
		parent_topic.remove_subtopic_observers.remove(self._remove_topic_node)
		parent_topic.name_changed_observers.remove(self._rename_topic_node)

		for subtopic in parent_topic.subtopics:
			self._remove_observers(subtopic)
	
	def _init_passage_list_ctrl_headers(self):
		self.passage_list_ctrl.InsertColumn(0, _("Passage"))
		self.passage_list_ctrl.InsertColumn(1, _("Comment"))

	@guiutil.frozen
	def _setup_passage_list_ctrl(self):
		self._remove_passage_list_observers()
		self.passage_list_ctrl.DeleteAllItems()
		self._passage_list_topic = self.selected_topic
		if self._passage_list_topic is None:
			return

		self._add_passage_list_observers()
		for index, passage_entry in enumerate(self.selected_topic.passages):
			self._insert_topic_passage(passage_entry, index)

		# No longer needed, since we allow multiple selection.
		#if self.selected_topic.passages:
		#	self._select_list_entry_by_index(0)

	def _add_passage_list_observers(self):
		self._passage_list_topic.add_passage_observers += self._insert_topic_passage
		self._passage_list_topic.remove_passage_observers += self._remove_topic_passage
		self._passage_list_topic.passage_order_changed_observers += self._passage_order_changed

	def _remove_passage_list_observers(self):
		if self._passage_list_topic is None:
			return

		self._passage_list_topic.add_passage_observers -= self._insert_topic_passage
		self._passage_list_topic.remove_passage_observers -= self._remove_topic_passage
		self._passage_list_topic.passage_order_changed_observers -= self._passage_order_changed
		for passage in self._passage_list_topic.passages:
			self._remove_passage_list_passage_observers(passage)

	def _insert_topic_passage(self, passage_entry, index=None):
		if index is None:
			index = self._passage_list_topic.passages.index(passage_entry)
		self._add_passage_list_passage_observers(passage_entry)
		self.passage_list_ctrl.InsertStringItem(index, _passage_str(passage_entry, short=True))
		self.passage_list_ctrl.SetStringItem(index, 1, passage_entry.comment[:300].replace("\n", " "))

	def _remove_topic_passage(self, passage_entry, index):
		self.passage_list_ctrl.DeleteItem(index)
		self._remove_passage_list_passage_observers(passage_entry)
		# XXX: This means the whole selection is being rebuilt every time
		# selection changes (e.g. when an item is deleted).  Though we freeze
		# the GUI this can't be efficient.
		# This is the last passage to be removed.
		if not self.selected_passages:
			if not self._passage_list_topic.passages:
				self.selected_passages = []
			else:
				if len(self._passage_list_topic.passages) == index:
					index -= 1
				self._select_list_entry_by_index(index)

	def _passage_order_changed(self, order_passages_by):
		self._setup_passage_list_ctrl()
		# Preserve the selection.
		pass

	def _add_passage_list_passage_observers(self, passage_entry):
		passage_entry.passage_changed_observers.add_observer(self._change_passage_passage, (passage_entry,))
		passage_entry.comment_changed_observers.add_observer(self._change_passage_comment, (passage_entry,))

	def _remove_passage_list_passage_observers(self, passage_entry):
		passage_entry.passage_changed_observers -= self._change_passage_passage
		passage_entry.comment_changed_observers -= self._change_passage_comment

	def _change_passage_passage(self, passage_entry, new_passage):
		index = self.selected_topic.passages.index(passage_entry)
		self.passage_list_ctrl.SetStringItem(index, 0,
				_passage_str(passage_entry, short=True))

	def _change_passage_comment(self, passage_entry, new_comment):
		index = self.selected_topic.passages.index(passage_entry)
		self.passage_list_ctrl.SetStringItem(index, 1, passage_entry.comment[:300].replace("\n", " "))

	def _passage_selection_changed(self, event):
		self._change_selected_passages()

	def _change_selected_passages(self):
		self.passage_list_ctrl.SetFocus()
		self.selected_passages = self._find_selected_passages()
		self._change_passage_details(self.selected_passages)

	def _find_selected_passages(self):
		topic_passages = self._passage_list_topic.passages
		selected_passages = []
		index = -1
		while 1:
			index = self.passage_list_ctrl.GetNextItem(
					index, wx.LIST_NEXT_ALL, wx.LIST_STATE_SELECTED,
				)
			if index == -1:
				break

			try:
				selected_passages.append(topic_passages[index])
			# Sometimes (when deleting, etc.) it has indices that are valid
			# list indices but not passage indices.  Just ignore these.
			except IndexError:
				pass
		return selected_passages

	def _passage_activated(self, event):
		passage_entry = self.selected_topic.passages[event.GetIndex()]
		guiconfig.mainfrm.set_bible_ref(str(passage_entry), source=events.TOPIC_LIST)
		guiconfig.mainfrm.Raise()

	def select_passages(self, passages, focused_passage):
		"""Selects the given passages in the passage list control.

		Sets the given passage to be the passage on which keyboard focus is.
		"""
		topic_passages = self._passage_list_topic.passages
		passage_indexes = [topic_passages.index(passage) for passage in passages]
		focused_passage_index = topic_passages.index(focused_passage)

		index = -1
		while 1:
			index = self.passage_list_ctrl.GetNextItem(index)
			if index == -1:
				break

			current_state = self.passage_list_ctrl.GetItemState(index, wx.LIST_STATE_SELECTED)
			if index in passage_indexes:
				state = wx.LIST_STATE_SELECTED
				if index == focused_passage_index:
					state |= wx.LIST_STATE_FOCUSED
				self.passage_list_ctrl.SetItemState(index, state, state)
			elif current_state & wx.LIST_STATE_SELECTED:
				self.passage_list_ctrl.SetItemState(index, 0, wx.LIST_STATE_SELECTED)

	def _select_list_entry_by_index(self, index):
		"""Selects the entry in the list control with the given index."""
		state = wx.LIST_STATE_SELECTED | wx.LIST_STATE_FOCUSED
		self.passage_list_ctrl.SetItemState(index, state, state)

	@guiutil.frozen
	def _select_all_passages(self):
		if self._passage_list_topic is None:
			return

		for index in range(len(self._passage_list_topic.passages)):
			self._select_list_entry_by_index(index)
	
	def _show_passage_context_menu(self, event):
		"""Shows the context menu for a passage in the passage list."""
		self._change_selected_passages()
		sel = event.GetIndex() != -1
		menu = wx.Menu()
		
		item = menu.Append(wx.ID_ANY, _("&Open"))
		self.Bind(wx.EVT_MENU,
				lambda e: self._passage_activated(event),
				id=item.Id)
		item.Enable(sel)
		
		menu.AppendSeparator()
		
		item = menu.Append(wx.ID_ANY, _("Cu&t"))
		self.Bind(wx.EVT_MENU,
				lambda e: self._operations_manager.cut(),
				id=item.Id)
		item.Enable(sel)

		item = menu.Append(wx.ID_ANY, _("&Copy"))
		self.Bind(wx.EVT_MENU,
				lambda e: self._operations_manager.copy(),
				id=item.Id)
		item.Enable(sel)

		item = menu.Append(wx.ID_ANY, _("&Paste"))
		self.Bind(wx.EVT_MENU,
				lambda e: self._safe_paste(),
				id=item.Id)

		menu.AppendSeparator()
		
		item = menu.Append(wx.ID_ANY, _("&Delete"))
		self.Bind(wx.EVT_MENU,
				lambda e: self._delete(),
				id=item.Id)
		item.Enable(sel)
		
		self.passage_list_ctrl.PopupMenu(menu)

	def _topic_tree_got_focus(self, event):
		self.is_passage_selected = False
		self._change_topic_details(self.selected_topic)
		event.Skip()

	def _passage_list_got_focus(self, event):
		self.is_passage_selected = True
		self._change_passage_details(self.selected_passages)
		event.Skip()

	def _setup_item_details_panel(self):
		self.topic_details_panel = TopicDetailsPanel(
				self.item_details_panel, self._operations_manager
			)
		self.topic_details_panel.Hide()
		self.item_details_panel.Sizer.Add(self.topic_details_panel, 1, wx.GROW)
		self.passage_details_panel = PassageDetailsPanel(
				self.item_details_panel, self._operations_manager
			)
		self.passage_details_panel.Hide()
		self.item_details_panel.Sizer.Add(self.passage_details_panel, 1, wx.GROW)
		self.item_details_panel_text = wx.StaticText(self.item_details_panel)
		self.item_details_panel.Sizer.Add(self.item_details_panel_text, 1, wx.ALIGN_CENTRE)
		self.item_details_panel_text.Hide()

	def _change_topic_details(self, new_topic):
		if new_topic is None:
			if self.topic_details_panel.IsShown():
				self._display_no_items_selected()
			return

		if new_topic is self._manager:
			self._display_text_in_panel(_("All topics."))
			return

		self.topic_details_panel.set_topic(new_topic)
		self._switch_item_details_current_panel(self.topic_details_panel)

	def _change_passage_details(self, selected_passages):
		if not selected_passages:
			if self.passage_details_panel.IsShown():
				self._display_no_items_selected()
			return
		elif len(selected_passages) == 1:
			self.passage_details_panel.set_passage(selected_passages[0])
			self._switch_item_details_current_panel(self.passage_details_panel)
		else:
			self._display_text_in_panel(
					_(u"%d passages selected.\n\nSelect a passage to view it.") % len(selected_passages)
				)

	def _display_no_items_selected(self):
		"""When no items have been selected, display this to the user."""
		self._display_text_in_panel(_("No items have been selected."))

	def _display_text_in_panel(self, text):
		"""Displays the given text in the panel instead of either the current
		passage or the current topic.
		"""
		self.item_details_panel_text.Label = text
		self._switch_item_details_current_panel(self.item_details_panel_text)

	@guiutil.frozen
	def _switch_item_details_current_panel(self, new_panel):
		"""Makes the given panel the currently displayed item details panel."""
		# Avoid dead object errors.
		if not self:
			return

		assert new_panel in self.item_details_panel.Children
		for window in self.item_details_panel.Children:
			if window is not new_panel:
				window.Hide()
		new_panel.Show()
		self.item_details_panel.Sizer.Layout()