class SafeImageListTest(unittest.TestCase, BaseOutWikerGUIMixin): def setUp(self): self.initApplication() self.width = 16 self.height = 16 self.imagelist = SafeImageList(self.width, self.height) def tearDown(self): self.destroyApplication() def _addImage(self, fname): bitmap = wx.Bitmap(fname) self.imagelist.Add(bitmap) def test_16x16(self): self._addImage('../test/images/16x16.png') size = self.imagelist.GetSize(0) self.assertEqual(size[0], self.width) self.assertEqual(size[1], self.height) def test_15x15(self): self._addImage('../test/images/15x15.png') size = self.imagelist.GetSize(0) self.assertEqual(size[0], self.width) self.assertEqual(size[1], self.height) def test_15x16(self): self._addImage('../test/images/15x16.png') size = self.imagelist.GetSize(0) self.assertEqual(size[0], self.width) self.assertEqual(size[1], self.height) def test_16x15(self): self._addImage('../test/images/16x15.png') size = self.imagelist.GetSize(0) self.assertEqual(size[0], self.width) self.assertEqual(size[1], self.height) def test_17x16(self): self._addImage('../test/images/17x16.png') size = self.imagelist.GetSize(0) self.assertEqual(size[0], self.width) self.assertEqual(size[1], self.height) def test_16x17(self): self._addImage('../test/images/16x17.png') size = self.imagelist.GetSize(0) self.assertEqual(size[0], self.width) self.assertEqual(size[1], self.height) def test_17x17(self): self._addImage('../test/images/17x17.png') size = self.imagelist.GetSize(0) self.assertEqual(size[0], self.width) self.assertEqual(size[1], self.height)
class SafeImageListTest(unittest.TestCase): def setUp(self): self.width = 16 self.height = 16 self.imagelist = SafeImageList(self.width, self.height) def _addImage(self, fname): bitmap = wx.Bitmap(fname) self.imagelist.Add(bitmap) def test_16x16(self): self._addImage(u'../test/images/16x16.png') size = self.imagelist.GetSize(0) self.assertEqual(size[0], self.width) self.assertEqual(size[1], self.height) def test_15x15(self): self._addImage(u'../test/images/15x15.png') size = self.imagelist.GetSize(0) self.assertEqual(size[0], self.width) self.assertEqual(size[1], self.height) def test_15x16(self): self._addImage(u'../test/images/15x16.png') size = self.imagelist.GetSize(0) self.assertEqual(size[0], self.width) self.assertEqual(size[1], self.height) def test_16x15(self): self._addImage(u'../test/images/16x15.png') size = self.imagelist.GetSize(0) self.assertEqual(size[0], self.width) self.assertEqual(size[1], self.height) def test_17x16(self): self._addImage(u'../test/images/17x16.png') size = self.imagelist.GetSize(0) self.assertEqual(size[0], self.width) self.assertEqual(size[1], self.height) def test_16x17(self): self._addImage(u'../test/images/16x17.png') size = self.imagelist.GetSize(0) self.assertEqual(size[0], self.width) self.assertEqual(size[1], self.height) def test_17x17(self): self._addImage(u'../test/images/17x17.png') size = self.imagelist.GetSize(0) self.assertEqual(size[0], self.width) self.assertEqual(size[1], self.height)
class EditSnippetsDialog(TestedDialog): ''' Dialog to create, edit and remove snippets and folders. ''' def __init__(self, parent): super(EditSnippetsDialog, self).__init__(parent, style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER) global _ _ = get_() self.ICON_WIDTH = 16 self.ICON_HEIGHT = 16 self._imagesPath = getImagesPath() self._dirImageId = None self._snippetImageId = None self.addGroupBtn = None self.addSnippetBtn = None self._varMenuItems = [ (_(u'Selected text'), defines.VAR_SEL_TEXT), (_(u'Current date'), defines.VAR_DATE), (_(u'Page title'), defines.VAR_TITLE), (_(u'Page type'), defines.VAR_PAGE_TYPE), (_(u'Page tags list'), defines.VAR_TAGS), (_(u'Attachments path'), defines.VAR_ATTACH), (_(u'Path to page'), defines.VAR_FOLDER), (_(u'Relative page path'), defines.VAR_SUBPATH), (_(u'Page creation date'), defines.VAR_DATE_CREATING), (_(u'Page modification date'), defines.VAR_DATE_EDITIND), (_(u'Page Id'), defines.VAR_PAGE_ID), (_(u'Attachments list'), defines.VAR_ATTACHLIST), (_(u'Child pages'), defines.VAR_CHILDLIST), ] self._blocksMenuItems = [ (_('{% if %}'), (u'{% if %}', '{% elif %}{% else %}{% endif %}')), (_('{% include %}'), (u"{% include '", u"' %}")), (_('{# comment #}'), (u'{# ', ' #}')), ] self._createGUI() self.SetTitle(_(u'Snippets management')) self.disableSnippetEditor() def disableSnippetEditor(self): self.snippetEditor.SetText(u'') self._snippetPanel.Disable() self.runSnippetBtn.Disable() def enableSnippetEditor(self): self._snippetPanel.Enable() self.runSnippetBtn.Enable() def appendDirTreeItem(self, parentItem, name, data): itemData = wx.TreeItemData(data) if parentItem is not None: newItemId = self.snippetsTree.AppendItem(parentItem, name, self._dirImageId, data=itemData) else: newItemId = self.snippetsTree.AddRoot(name, self._dirImageId, data=itemData) return newItemId def appendSnippetTreeItem(self, parentItem, name, data): itemData = wx.TreeItemData(data) newItemId = self.snippetsTree.AppendItem(parentItem, name, self._snippetImageId, data=itemData) return newItemId def _createTreeButtons(self, groupButtonsSizer): # Add a group button self.addGroupBtn = wx.BitmapButton( self, bitmap=wx.Bitmap(os.path.join(self._imagesPath, "folder_add.png"))) self.addGroupBtn.SetToolTipString(_(u"Add new snippets group")) groupButtonsSizer.Add(self.addGroupBtn, flag=wx.ALL, border=0) # Add a snippet button self.addSnippetBtn = wx.BitmapButton( self, bitmap=wx.Bitmap(os.path.join(self._imagesPath, "snippet_add.png"))) self.addSnippetBtn.SetToolTipString(_(u"Create new snippet")) groupButtonsSizer.Add(self.addSnippetBtn, flag=wx.ALL, border=0) # Rename group or snippet button self.renameBtn = wx.BitmapButton( self, bitmap=wx.Bitmap(os.path.join(self._imagesPath, "rename.png"))) self.renameBtn.SetToolTipString(_(u"Rename")) groupButtonsSizer.Add(self.renameBtn, flag=wx.ALL, border=0) # Remove group or snippet button self.removeBtn = wx.BitmapButton( self, bitmap=wx.Bitmap(os.path.join(self._imagesPath, "remove.png"))) self.removeBtn.SetToolTipString(_(u"Remove")) groupButtonsSizer.Add(self.removeBtn, flag=wx.ALL, border=0) # Run snippet self.runSnippetBtn = wx.BitmapButton( self, bitmap=wx.Bitmap(os.path.join(self._imagesPath, "run.png"))) self.runSnippetBtn.SetToolTipString(_(u"Run snippet")) groupButtonsSizer.Add(self.runSnippetBtn, flag=wx.ALL, border=0) # Open help self.openHelpBtn = wx.BitmapButton( self, bitmap=wx.Bitmap(os.path.join(self._imagesPath, "help.png"))) self.openHelpBtn.SetToolTipString(_(u"Open help...")) groupButtonsSizer.Add(self.openHelpBtn, flag=wx.ALL, border=0) def _createImagesList(self): self._imagelist = SafeImageList(self.ICON_WIDTH, self.ICON_HEIGHT) self._dirImageId = self._imagelist.Add( wx.Bitmap(os.path.join(self._imagesPath, u'folder.png'))) self._snippetImageId = self._imagelist.Add( wx.Bitmap(os.path.join(self._imagesPath, u'snippet.png'))) def _createTreePanel(self, mainSizer): self._createImagesList() self.snippetsTree = wx.TreeCtrl(self, style=wx.TR_HAS_BUTTONS | wx.TR_EDIT_LABELS | wx.SUNKEN_BORDER) self.snippetsTree.SetMinSize((200, 200)) self.snippetsTree.AssignImageList(self._imagelist) # Buttons for the snippets tree groupButtonsSizer = wx.BoxSizer(wx.HORIZONTAL) self._createTreeButtons(groupButtonsSizer) # TreeSizer treeSizer = wx.FlexGridSizer(cols=1) treeSizer.AddGrowableRow(1) treeSizer.AddGrowableCol(0) treeSizer.Add(groupButtonsSizer, 1, wx.EXPAND, border=2) treeSizer.Add(self.snippetsTree, 1, wx.EXPAND, border=2) mainSizer.Add(treeSizer, 1, wx.ALL | wx.EXPAND, border=2) def _createSnippetButtons(self, snippetButtonsSizer, parent): # Insert variable self.insertVariableBtn = PopupButton(parent, bitmap=wx.Bitmap( os.path.join( self._imagesPath, "variables-menu.png"))) self.insertVariableBtn.SetToolTipString(_(u"Insert variable")) for menuitem in self._varMenuItems: data = u'{{' + menuitem[1] + u'}}' title = u'{var} - {title}'.format(var=data, title=menuitem[0]) self.insertVariableBtn.appendMenuItem(title, data) self.insertVariableBtn.appendMenuItem(_(u'Other variable...'), None) snippetButtonsSizer.Add(self.insertVariableBtn, flag=wx.ALL, border=0) # Insert block self.insertBlockBtn = PopupButton( parent, bitmap=wx.Bitmap(os.path.join(self._imagesPath, "block-menu.png"))) self.insertBlockBtn.SetToolTipString(_(u"Insert block")) for menuitem in self._blocksMenuItems: data = menuitem[1] title = menuitem[0] self.insertBlockBtn.appendMenuItem(title, data) snippetButtonsSizer.Add(self.insertBlockBtn, flag=wx.ALL, border=0) def _createSnippetPanel(self, mainSizer): self._snippetPanel = wx.Panel(self) # Snippet editor self.snippetEditor = SnippetEditor(self._snippetPanel) # Buttons for snippet snippetButtonsSizer = wx.BoxSizer(wx.HORIZONTAL) self._createSnippetButtons(snippetButtonsSizer, self._snippetPanel) # Errors messages self.errorsTextCtrl = wx.TextCtrl(self._snippetPanel, style=wx.TE_MULTILINE | wx.TE_READONLY) self.errorsTextCtrl.SetMinSize((-1, 100)) # SnippetSizer snippetSizer = wx.FlexGridSizer(cols=1) snippetSizer.AddGrowableRow(1) snippetSizer.AddGrowableCol(0) snippetSizer.Add(snippetButtonsSizer, 1, wx.EXPAND, border=2) snippetSizer.Add(self.snippetEditor, 1, wx.EXPAND, border=2) snippetSizer.Add(self.errorsTextCtrl, 1, wx.EXPAND, border=2) self._snippetPanel.SetSizer(snippetSizer) mainSizer.Add(self._snippetPanel, 1, wx.ALL | wx.EXPAND, border=2) def _createBottomButtons(self, mainSizer): mainSizer.AddStretchSpacer() self.closeBtn = wx.Button(self, id=wx.ID_CLOSE, label=_(u'Close')) mainSizer.Add(self.closeBtn, flag=wx.ALL | wx.ALIGN_RIGHT, border=2) self.SetEscapeId(wx.ID_CLOSE) def _createGUI(self): # Main Sizer mainSizer = wx.FlexGridSizer(cols=2) mainSizer.AddGrowableCol(0, 1) mainSizer.AddGrowableCol(1, 3) mainSizer.AddGrowableRow(0) self._createTreePanel(mainSizer) self._createSnippetPanel(mainSizer) self._createBottomButtons(mainSizer) self.SetSizer(mainSizer) self.Layout() @property def currentSnippet(self): return self.snippetEditor.GetText() def setError(self, text): self.errorsTextCtrl.SetValue(text)
class IconsetPanel(BasePrefPanel): def __init__(self, parent): super(type(self), self).__init__(parent) self._default_group_cover = os.path.join(getImagesDir(), u'icons_cover_default.png') self.__createGuiElements() self._groups.Bind(wx.EVT_TREE_SEL_CHANGED, handler=self.__onGroupSelect) self._groups.Bind(wx.EVT_TREE_BEGIN_LABEL_EDIT, self.__onBeginLabelEdit) self._groups.Bind(wx.EVT_TREE_END_LABEL_EDIT, self.__onEndLabelEdit) self._groups.Bind(wx.EVT_KEY_DOWN, handler=self.__onKeyDown) self.__updateGroups() self.SetupScrolling() def __createGuiElements(self): mainSizer = wx.FlexGridSizer(cols=2, rows=1, vgap=0, hgap=0) mainSizer.AddGrowableCol(1) mainSizer.AddGrowableRow(0) # # Controls for groups groupsSizer = wx.FlexGridSizer(cols=1, rows=0, vgap=0, hgap=0) groupsSizer.AddGrowableCol(0) groupsSizer.AddGrowableRow(0) self._groups = wx.TreeCtrl( self, style=wx.TR_HAS_BUTTONS | wx.TR_EDIT_LABELS | wx.SUNKEN_BORDER) self._groups.SetMinSize((200, 200)) self._imagelist = SafeImageList(ICON_WIDTH, ICON_HEIGHT) self._groups.AssignImageList(self._imagelist) imagesDir = getImagesDir() # Buttons for groups groupButtonsSizer = wx.BoxSizer(wx.HORIZONTAL) # Add a group self.addGroupBtn = wx.BitmapButton( self, bitmap=wx.Bitmap(os.path.join(imagesDir, "add.png")) ) self.addGroupBtn.SetToolTip(_(u"Add new group")) self.addGroupBtn.Bind(wx.EVT_BUTTON, handler=self.__onAddGroup) # Remove a group self.removeGroupBtn = wx.BitmapButton( self, bitmap=wx.Bitmap(os.path.join(imagesDir, "remove.png")) ) self.removeGroupBtn.SetToolTip(_(u"Remove group")) self.removeGroupBtn.Bind(wx.EVT_BUTTON, handler=self.__onRemoveGroup) # Rename a group self.renameGroupBtn = wx.BitmapButton( self, bitmap=wx.Bitmap(os.path.join(imagesDir, "pencil.png")) ) self.renameGroupBtn.SetToolTip(_(u"Rename group")) self.renameGroupBtn.Bind(wx.EVT_BUTTON, handler=self.__onRenameGroup) groupButtonsSizer.Add(self.addGroupBtn, flag=wx.ALL, border=0) groupButtonsSizer.Add(self.removeGroupBtn, flag=wx.ALL, border=0) groupButtonsSizer.Add(self.renameGroupBtn, flag=wx.ALL, border=0) groupsSizer.Add(self._groups, 1, wx.RIGHT | wx.EXPAND, border=2) groupsSizer.Add(groupButtonsSizer, 1, wx.RIGHT | wx.EXPAND, border=2) # # Controls for icons in the group iconsSizer = wx.FlexGridSizer(cols=1, rows=0, vgap=0, hgap=0) iconsSizer.AddGrowableRow(0) iconsSizer.AddGrowableCol(0) self._iconsList = IconListCtrl(self, True) self._iconsList.SetMinSize((200, 150)) # Buttons for icons in the group iconsButtonsSizer = wx.BoxSizer(wx.HORIZONTAL) # Add icons self.addIconsBtn = wx.BitmapButton( self, bitmap=wx.Bitmap(os.path.join(imagesDir, "add.png")) ) self.addIconsBtn.SetToolTip(_(u"Add icons")) self.addIconsBtn.Bind(wx.EVT_BUTTON, handler=self.__onAddIcons) # Remove icons self.removeIconsBtn = wx.BitmapButton( self, bitmap=wx.Bitmap(os.path.join(imagesDir, "remove.png")) ) self.removeIconsBtn.SetToolTip(_(u"Remove selected icons")) self.removeIconsBtn.Bind(wx.EVT_BUTTON, handler=self.__onRemoveIcons) # Set icon as group cover self.setCoverBtn = wx.BitmapButton( self, bitmap=wx.Bitmap(os.path.join(imagesDir, "picture.png")) ) self.setCoverBtn.SetToolTip(_(u"Set icon as group cover")) self.setCoverBtn.Bind(wx.EVT_BUTTON, handler=self.__onSetCover) iconsButtonsSizer.Add(self.addIconsBtn, flag=wx.ALL, border=0) iconsButtonsSizer.Add(self.removeIconsBtn, flag=wx.ALL, border=0) iconsButtonsSizer.Add(self.setCoverBtn, flag=wx.ALL, border=0) iconsSizer.Add(self._iconsList, 1, wx.LEFT | wx.EXPAND, border=2) iconsSizer.Add(iconsButtonsSizer, 1, wx.LEFT | wx.EXPAND, border=2) # Main sizer mainSizer.Add(groupsSizer, 1, wx.ALL | wx.EXPAND, border=0) mainSizer.Add(iconsSizer, 1, wx.ALL | wx.EXPAND, border=0) self.SetSizer(mainSizer) self.Layout() def Save(self): pass def LoadState(self): pass def __updateGroups(self): self._groups.DeleteAllItems() self._imagelist.RemoveAll() collection = self.__getIconsCollection() # Add the root element rootimage = collection.getCover(None) imageIndex = -1 if rootimage is None else self._imagelist.Add(wx.Bitmap(rootimage)) rootItem = self._groups.AddRoot(_(u"Not in groups"), imageIndex, data=None) # Add child groups for group in collection.getGroups(): image = collection.getCover(group) if image is None: image = self._default_group_cover imageIndex = self._imagelist.Add(wx.Bitmap(image)) self._groups.AppendItem(rootItem, group, imageIndex, data=group) self._groups.Expand(rootItem) self._groups.SelectItem(rootItem) self.__onGroupSelect(None) def __getIconsCollection(self): return IconsCollection(getIconsDirList()[-1]) def __showIcons(self, groupname): """ Show icons from group groupname. If groupname is None then icons from root will be showed """ self._iconsList.clear() collection = self.__getIconsCollection() icons = collection.getIcons(groupname) self._iconsList.setIconsList(icons) def __onGroupSelect(self, event): """ User select other group """ selItem = self._groups.GetSelection() if not selItem.IsOk(): return group = self._groups.GetItemData(selItem) self.__showIcons(group) def __onAddGroup(self, event): collection = self.__getIconsCollection() newGroupName = self.__getNewGroupName(collection.getGroups()) try: collection.addGroup(newGroupName) self.__updateGroups() self.__selectGroupItem(newGroupName) except(IOError, SystemError): MessageBox( _(u"Can't create directory for icons group"), _(u"Error"), wx.OK | wx.ICON_ERROR) def __selectGroupItem(self, groupname): """ Select group in _groups tree. If groupname is None then select the root element. If groupname not exists then method does nothing. """ rootItem = self._groups.GetRootItem() assert rootItem.IsOk() if groupname is None: self._groups.SelectItem(rootItem) nextGroupItem, cookie = self._groups.GetFirstChild(rootItem) while nextGroupItem.IsOk(): if self._groups.GetItemData(nextGroupItem) == groupname: self._groups.SelectItem(nextGroupItem) break nextGroupItem, cookie = self._groups.GetNextChild(rootItem, cookie) def __getNewGroupName(self, groups): """ Return name for new group """ newGroupTemplate = _(u"New group{}") newGroupName = newGroupTemplate.format(u"") if newGroupName in groups: # Generate new group name in format "New group(1)", # "New group(2)" etc index = 0 while newGroupName in groups: index += 1 newGroupName = newGroupTemplate.format(u"({})".format(index)) return newGroupName def __onEndLabelEdit(self, event): if event.IsEditCancelled(): return event.Veto() oldGroupName = self._groups.GetItemData(event.GetItem()) newGroupName = event.GetLabel().strip() assert oldGroupName is not None collection = self.__getIconsCollection() try: collection.renameGroup(oldGroupName, newGroupName) except(IOError, SystemError): MessageBox( _(u"Can't rename directory for icons group"), _(u"Error"), wx.OK | wx.ICON_ERROR) return except DuplicateGroupError: MessageBox( _(u'Group with name "{}" exists already').format(newGroupName), _(u"Error"), wx.OK | wx.ICON_ERROR) return except ValueError: MessageBox( _(u'Invalid group name "{}"').format(newGroupName), _(u"Error"), wx.OK | wx.ICON_ERROR) return self.__updateGroups() self.__selectGroupItem(newGroupName) def __onBeginLabelEdit(self, event): item = event.GetItem() group = self._groups.GetItemData(item) if group is None: # Root element event.Veto() def __onRenameGroup(self, event): selItem = self._groups.GetSelection() rootItem = self._groups.GetRootItem() if selItem.IsOk() and selItem != rootItem: self._groups.EditLabel(selItem) def __onRemoveGroup(self, event): selItem = self._groups.GetSelection() rootItem = self._groups.GetRootItem() if not selItem.IsOk() or selItem == rootItem: return groupname = self._groups.GetItemData(selItem) assert groupname is not None if MessageBox( _(u'Remove group "{}" and all icons inside it?').format(groupname), _(u"Remove group?"), wx.YES_NO | wx.ICON_QUESTION) == wx.YES: try: self.__getIconsCollection().removeGroup(groupname) except(IOError, SystemError): MessageBox( _(u"Can't remove group directory"), _(u"Error"), wx.OK | wx.ICON_ERROR) return self.__updateGroups() def __onAddIcons(self, event): wildcard = u"{images}(*.png; *.jpg; *.jpeg; *.gif; *.bmp)|*.png; *.jpg; *.jpeg; *.gif; *.bmp|*.png|*.png|*.jpg; *.jpeg|*.jpg;*.jpeg|*.gif|*.gif|*.bmp|*.bmp|{all}(*.*)|*.*".format( images=_(u"All image files"), all=_(u"All files")) style = wx.FD_OPEN | wx.FD_MULTIPLE | wx.FD_FILE_MUST_EXIST with TestedFileDialog( self, _(u"Select images"), wildcard=wildcard, style=style) as dlg: if dlg.ShowModal() == wx.ID_OK: item = self._groups.GetSelection() group = self._groups.GetItemData(item) collection = self.__getIconsCollection() collection.addIcons(group, dlg.GetPaths()) self.__updateGroups() self.__selectGroupItem(group) def __onRemoveIcons(self, event): icons = self._iconsList.getSelection() if not icons: MessageBox( _(u"You have not selected any icons"), _(u"Select icons"), wx.OK | wx.ICON_INFORMATION) return if MessageBox( _(u"Remove selected icons?"), _(u"Remove icons"), wx.YES_NO | wx.ICON_QUESTION) == wx.YES: for fname in icons: try: os.remove(fname) except(IOError, SystemError): pass item = self._groups.GetSelection() group = self._groups.GetItemData(item) self.__updateGroups() self.__selectGroupItem(group) def __onSetCover(self, event): icons = self._iconsList.getSelection() if not icons: MessageBox( _(u"You have not selected any icons"), _(u"Select icons"), wx.OK | wx.ICON_ERROR) return item = self._groups.GetSelection() group = self._groups.GetItemData(item) collection = self.__getIconsCollection() collection.setCover(group, icons[0]) self.__updateGroups() self.__selectGroupItem(group) def __onKeyDown(self, event): if (event.GetKeyCode() == wx.WXK_F2 and not event.AltDown() and not event.CmdDown() and not event.ControlDown() and not event.ShiftDown()): self.__onRenameGroup(None)
class NotesTree(wx.Panel): def __init__(self, parent, application): super().__init__(parent, style=wx.TAB_TRAVERSAL) self._application = application # Переключатель, устанавливается в True, # если "внезапно" изменяется текущая страница self.__externalPageSelect = False self.toolbar = wx.ToolBar(parent=self, style=wx.TB_HORIZONTAL | wx.TB_FLAT | wx.TB_DOCKABLE) treeStyle = (wx.TR_HAS_BUTTONS | wx.TR_EDIT_LABELS | wx.SUNKEN_BORDER) self.treeCtrl = wx.TreeCtrl(self, style=treeStyle) self.__set_properties() self.__do_layout() self.defaultIcon = os.path.join(outwiker.core.system.getImagesDir(), "page.png") self.iconHeight = ICON_HEIGHT self.defaultBitmap = wx.Bitmap(self.defaultIcon) assert self.defaultBitmap.IsOk() self.defaultBitmap.SetHeight(self.iconHeight) # Key - path to icon, value - icon ID in self.imagelist self._iconsCache = {} self.dragItem = None # Картинки для дерева self.imagelist = SafeImageList(ICON_WIDTH, self.iconHeight) self.treeCtrl.AssignImageList(self.imagelist) # Кеш для страниц, чтобы было проще искать элемент дерева по странице # Словарь. Ключ - страница, значение - элемент дерева wx.TreeItemId self._pageCache = {} self.popupMenu = None # Секция настроек куда сохраняем развернутость страницы self.pageOptionsSection = u"Tree" # Имя опции для сохранения развернутости страницы self.pageOptionExpand = "Expand" self.__BindApplicationEvents() self.__BindGuiEvents() def getTreeItem(self, page): """ Получить элемент дерева по странице. Если для страницы не создан элемент дерева, возвращается None """ if page in self._pageCache: return self._pageCache[page] def __BindApplicationEvents(self): """ Подписка на события контроллера """ self._application.onWikiOpen += self.__onWikiOpen self._application.onTreeUpdate += self.__onTreeUpdate self._application.onPageCreate += self.__onPageCreate self._application.onPageOrderChange += self.__onPageOrderChange self._application.onPageSelect += self.__onPageSelect self._application.onPageRemove += self.__onPageRemove self._application.onPageUpdate += self.__onPageUpdate self._application.onStartTreeUpdate += self.__onStartTreeUpdate self._application.onEndTreeUpdate += self.__onEndTreeUpdate def __UnBindApplicationEvents(self): """ Отписка от событий контроллера """ self._application.onWikiOpen -= self.__onWikiOpen self._application.onTreeUpdate -= self.__onTreeUpdate self._application.onPageCreate -= self.__onPageCreate self._application.onPageOrderChange -= self.__onPageOrderChange self._application.onPageSelect -= self.__onPageSelect self._application.onPageRemove -= self.__onPageRemove self._application.onPageUpdate -= self.__onPageUpdate self._application.onStartTreeUpdate -= self.__onStartTreeUpdate self._application.onEndTreeUpdate -= self.__onEndTreeUpdate def __onWikiOpen(self, root): self.__treeUpdate(root) def __onPageUpdate(self, sender, **kwargs): change = kwargs['change'] if change == PAGE_UPDATE_ICON: self.__updateIcon(sender) def __loadIcon(self, page): """ Добавляет иконку страницы в ImageList и возвращает ее идентификатор. Если иконки нет, то возвращает идентификатор иконки по умолчанию """ imageId = self.defaultImageId icon = page.icon if not icon: return imageId icon = os.path.abspath(icon) if icon in self._iconsCache: return self._iconsCache[icon] image = wx.Bitmap(icon) if image.IsOk(): imageId = self.imagelist.Add(image) page_path = os.path.abspath(page.path) if not icon.startswith(page_path): self._iconsCache[icon] = imageId return imageId def __updateIcon(self, page): if page not in self._pageCache: # Если нет этой страницы в дереве, то не важно, # изменилась иконка или нет return icon_id = self.__loadIcon(page) self.treeCtrl.SetItemImage(self._pageCache[page], icon_id) def __BindGuiEvents(self): """ Подписка на события интерфейса """ # События, связанные с деревом self.Bind(wx.EVT_TREE_SEL_CHANGED, self.__onSelChanged) self.Bind(wx.EVT_TREE_ITEM_MIDDLE_CLICK, self.__onMiddleClick) # Перетаскивание элементов self.treeCtrl.Bind(wx.EVT_TREE_BEGIN_DRAG, self.__onBeginDrag) self.treeCtrl.Bind(wx.EVT_TREE_END_DRAG, self.__onEndDrag) # Переименование элемента self.treeCtrl.Bind(wx.EVT_TREE_END_LABEL_EDIT, self.__onEndLabelEdit) # Показ всплывающего меню self.treeCtrl.Bind(wx.EVT_TREE_ITEM_MENU, self.__onPopupMenu) # Сворачивание/разворачивание элементов self.treeCtrl.Bind(wx.EVT_TREE_ITEM_COLLAPSED, self.__onTreeStateChanged) self.treeCtrl.Bind(wx.EVT_TREE_ITEM_EXPANDED, self.__onTreeStateChanged) self.treeCtrl.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.__onTreeItemActivated) self.Bind(wx.EVT_CLOSE, self.__onClose) def __onMiddleClick(self, event): item = event.GetItem() if not item.IsOk(): return page = self.treeCtrl.GetItemData(item) self._application.mainWindow.tabsController.openInTab(page, True) def __onClose(self, event): self.__UnBindApplicationEvents() self.treeCtrl.DeleteAllItems() self.imagelist.RemoveAll() self._iconsCache = {} self._removeButtons() self.toolbar.ClearTools() self.Destroy() def __onPageCreate(self, newpage): """ Обработка создания страницы """ if newpage.parent in self._pageCache: self.__insertChild(newpage) assert newpage in self._pageCache item = self._pageCache[newpage] assert item.IsOk() self.expand(newpage) def __onPageRemove(self, page): self.__removePageItem(page) def __onTreeItemActivated(self, event): item = event.GetItem() if not item.IsOk(): return page = self.treeCtrl.GetItemData(item) outwiker.gui.pagedialog.editPage(self, page) def __onTreeStateChanged(self, event): item = event.GetItem() assert item.IsOk() page = self.treeCtrl.GetItemData(item) self.__saveItemState(item) for child in page.children: self.__appendChildren(child) def __saveItemState(self, itemid): assert itemid.IsOk() page = self.treeCtrl.GetItemData(itemid) if page.readonly: return expanded = self.treeCtrl.IsExpanded(itemid) page_registry = page.root.registry.get_page_registry(page) page_registry.set(self.pageOptionExpand, expanded) def __getItemExpandState(self, page): """ Проверить, раскрыт ли элемент в дереве для страницы page """ if page is None: return True if page.parent is None: return True return self.treeCtrl.IsExpanded(self._pageCache[page]) def __getPageExpandState(self, page): """ Проверить состояние "раскрытости" страницы """ if page is None: return True if page.parent is None: return True page_registry = page.root.registry.get_page_registry(page) expanded = page_registry.getbool(self.pageOptionExpand, default=False) return expanded def __onPopupMenu(self, event): self.popupPage = None popupItem = event.GetItem() if not popupItem.IsOk(): return popupPage = self.treeCtrl.GetItemData(popupItem) self.popupMenu = PagePopupMenu(self, popupPage, self._application) self.PopupMenu(self.popupMenu.menu) def beginRename(self, page=None): """ Начать переименование страницы в дереве. Если page is None, то начать переименование текущей страницы """ pageToRename = page if page is not None else self._application.selectedPage if pageToRename is None or pageToRename.parent is None: outwiker.core.commands.MessageBox( _(u"You can't rename the root element"), _(u"Error"), wx.ICON_ERROR | wx.OK) return selectedItem = self._pageCache[pageToRename] if not selectedItem.IsOk(): return self.treeCtrl.EditLabel(selectedItem) def __onEndLabelEdit(self, event): if event.IsEditCancelled(): return # Новый заголовок label = event.GetLabel().strip() item = event.GetItem() page = self.treeCtrl.GetItemData(item) # Не доверяем переименовывать элементы системе event.Veto() outwiker.core.commands.renamePage(page, label) def __onStartTreeUpdate(self, root): self.__unbindUpdateEvents() def __unbindUpdateEvents(self): self._application.onTreeUpdate -= self.__onTreeUpdate self._application.onPageCreate -= self.__onPageCreate self._application.onPageSelect -= self.__onPageSelect self._application.onPageOrderChange -= self.__onPageOrderChange self.Unbind(wx.EVT_TREE_SEL_CHANGED, handler=self.__onSelChanged) def __onEndTreeUpdate(self, root): self.__bindUpdateEvents() self.__treeUpdate(self._application.wikiroot) def __bindUpdateEvents(self): self._application.onTreeUpdate += self.__onTreeUpdate self._application.onPageCreate += self.__onPageCreate self._application.onPageSelect += self.__onPageSelect self._application.onPageOrderChange += self.__onPageOrderChange self.Bind(wx.EVT_TREE_SEL_CHANGED, self.__onSelChanged) def __onBeginDrag(self, event): event.Allow() self.dragItem = event.GetItem() self.treeCtrl.SetFocus() def __onEndDrag(self, event): if self.dragItem is not None: # Элемент, на который перетащили другой элемент(self.dragItem) endDragItem = event.GetItem() # Перетаскиваемая станица draggedPage = self.treeCtrl.GetItemData(self.dragItem) # Будущий родитель для страницы if endDragItem.IsOk(): newParent = self.treeCtrl.GetItemData(endDragItem) # Moving page to itself is ignored if newParent != draggedPage: outwiker.core.commands.movePage(draggedPage, newParent) self.expand(newParent) self.dragItem = None def __onTreeUpdate(self, sender): self.__treeUpdate(sender.root) def __onPageSelect(self, page): """ Изменение выбранной страницы """ # Пометим, что изменение страницы произошло снаружи, # а не из-за клика по дереву self.__externalPageSelect = True try: currpage = self.selectedPage if currpage != page: self.selectedPage = page finally: self.__externalPageSelect = False def __onSelChanged(self, event): ctrlstate = wx.GetKeyState(wx.WXK_CONTROL) shiftstate = wx.GetKeyState(wx.WXK_SHIFT) if (ctrlstate or shiftstate) and not self.__externalPageSelect: self._application.mainWindow.tabsController.openInTab( self.selectedPage, True) else: self._application.selectedPage = self.selectedPage def __onPageOrderChange(self, sender): """ Изменение порядка страниц """ self.__updatePage(sender) @property def selectedPage(self): page = None item = self.treeCtrl.GetSelection() if item.IsOk(): page = self.treeCtrl.GetItemData(item) # Проверка того, что выбрали не корневой элемент if page.parent is None: page = None return page @selectedPage.setter def selectedPage(self, newSelPage): if newSelPage is None: item = self.treeCtrl.GetRootItem() else: self.__expandToPage(newSelPage) item = self.getTreeItem(newSelPage) if item is not None: self.treeCtrl.SelectItem(item) def __expandToPage(self, page): """ Развернуть ветви до того уровня, чтобы появился элемент для page """ # Список родительских страниц, которые нужно добавить в дерево pages = [] currentPage = page.parent while currentPage is not None: pages.append(currentPage) currentPage = currentPage.parent pages.reverse() for page in pages: self.expand(page) def __set_properties(self): self.SetSize((256, 260)) def __do_layout(self): mainSizer = wx.FlexGridSizer(cols=1) mainSizer.AddGrowableRow(1) mainSizer.AddGrowableCol(0) mainSizer.Add(self.toolbar, 1, wx.EXPAND, 0) mainSizer.Add(self.treeCtrl, 1, wx.EXPAND, 0) self.SetSizer(mainSizer) self.Layout() def expand(self, page): item = self.getTreeItem(page) if item is not None: self.treeCtrl.Expand(item) def __treeUpdate(self, rootPage): """ Обновить дерево """ self.treeCtrl.DeleteAllItems() self.imagelist.RemoveAll() self._iconsCache = {} self.defaultImageId = self.imagelist.Add(self.defaultBitmap) # Ключ - страница, значение - экземпляр класса TreeItemId self._pageCache = {} if rootPage is not None: rootname = os.path.basename(rootPage.path) rootItem = self.treeCtrl.AddRoot(rootname, data=rootPage, image=self.defaultImageId) self._pageCache[rootPage] = rootItem self.__mountItem(rootItem, rootPage) self.__appendChildren(rootPage) self.selectedPage = rootPage.selectedPage self.expand(rootPage) def __appendChildren(self, parentPage): """ Добавить детей в дерево parentPage - родительская страница, куда добавляем дочерние страницы """ grandParentExpanded = self.__getItemExpandState(parentPage.parent) if grandParentExpanded: for child in parentPage.children: if child not in self._pageCache: self.__insertChild(child) if self.__getPageExpandState(parentPage): self.expand(parentPage) def __mountItem(self, treeitem, page): """ Оформить элемент дерева в зависимости от настроек страницы (например, пометить только для чтения) """ if page.readonly: font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT) font.SetStyle(wx.FONTSTYLE_ITALIC) self.treeCtrl.SetItemFont(treeitem, font) def __insertChild(self, child): """ Вставить одну дочерниюю страницу(child) в ветвь """ parentItem = self.getTreeItem(child.parent) assert parentItem is not None item = self.treeCtrl.InsertItem(parentItem, child.order, child.display_title, data=child) self.treeCtrl.SetItemImage(item, self.__loadIcon(child)) self._pageCache[child] = item self.__mountItem(item, child) self.__appendChildren(child) return item def __removePageItem(self, page): """ Удалить элемент, соответствующий странице и все его дочерние страницы """ for child in page.children: self.__removePageItem(child) item = self.getTreeItem(page) if item is not None: del self._pageCache[page] self.treeCtrl.Delete(item) def __updatePage(self, page): """ Обновить страницу(удалить из списка и добавить снова) """ # Отпишемся от обновлений страниц, чтобы не изменять выбранную страницу self.__unbindUpdateEvents() self.treeCtrl.Freeze() try: self.__removePageItem(page) item = self.__insertChild(page) if page.root.selectedPage == page: # Если обновляем выбранную страницу self.treeCtrl.SelectItem(item) self.__scrollToCurrentPage() finally: self.treeCtrl.Thaw() self.__bindUpdateEvents() def __scrollToCurrentPage(self): """ Если текущая страница вылезла за пределы видимости, то прокрутить к ней """ selectedPage = self._application.selectedPage if selectedPage is None: return item = self.getTreeItem(selectedPage) if not self.treeCtrl.IsVisible(item): self.treeCtrl.ScrollTo(item) def addButtons(self): """ Add the buttons to notes tree panel. """ imagesDir = outwiker.core.system.getImagesDir() actionController = self._application.actionController actionController.appendToolbarButton( GoToParentAction.stringId, self.toolbar, os.path.join(imagesDir, "go_to_parent.png"), False) self.toolbar.AddSeparator() actionController.appendToolbarButton( MovePageDownAction.stringId, self.toolbar, os.path.join(imagesDir, "move_down.png"), False) actionController.appendToolbarButton( MovePageUpAction.stringId, self.toolbar, os.path.join(imagesDir, "move_up.png"), False) self.toolbar.AddSeparator() actionController.appendToolbarButton( AddSiblingPageAction.stringId, self.toolbar, os.path.join(imagesDir, "node-insert-next.png"), False) actionController.appendToolbarButton( AddChildPageAction.stringId, self.toolbar, os.path.join(imagesDir, "node-insert-child.png"), False) actionController.appendToolbarButton( RemovePageAction.stringId, self.toolbar, os.path.join(imagesDir, "node-delete.png"), False) self.toolbar.AddSeparator() actionController.appendToolbarButton( EditPagePropertiesAction.stringId, self.toolbar, os.path.join(imagesDir, "edit.png"), False) self.toolbar.Realize() self.Layout() def _removeButtons(self): actionController = self._application.actionController actions = [ GoToParentAction, MovePageDownAction, MovePageUpAction, AddSiblingPageAction, AddChildPageAction, RemovePageAction, EditPagePropertiesAction, ] for action in actions: actionController.removeToolbarButton(action.stringId)