Exemple #1
0
class PulldownMenu(Frame):

    # if using indentation then should use list as stack only
    # in other words only delete or insert at end

    # entries entries must have attribute text_attr or if this is
    # None then assume that entries entries themselves provide name

    def __init__(self,
                 parent,
                 callback=None,
                 entries=None,
                 text_attr=None,
                 colors=None,
                 outline='#602000',
                 fill='#B05848',
                 label_color='#501000',
                 selected_index=-1,
                 indent='',
                 extra='',
                 sticky='w',
                 do_initial_callback=True,
                 force_callback=False,
                 *args,
                 **kw):

        if (entries is None):
            entries = []

        Frame.__init__(self, parent, sticky=sticky, *args, **kw)

        self.text_attr = text_attr

        #indentation and extra string for popup information
        self.indent = indent
        self.extra = extra

        self.callback = callback
        self.do_initial_callback = do_initial_callback
        self.force_callback = force_callback

        frame = Frame(self)
        #frame = parent
        self.frame = frame
        self.bg = frame.cget('bg')
        self.outline = outline
        self.fill = fill
        self.label_color = label_color

        self.menu = Menu(parent, tearoff=0, borderwidth=1, activeborderwidth=1)
        self.menu.images = []
        self.label = Label(self, foreground=label_color)

        self.first_pass = True
        #self.entries = entries
        self.entries = []
        self.colors = []
        self.setup(entries, selected_index, colors=colors)

        s = 10
        self.canvas = Canvas(self, width=s, height=s, background=self.bg)

        self.label.bind("<Button-1>", self.labelPopup)
        # below does not work for some reason
        #self.bind("<Button-1>", self.labelPopup)

        # below does not work any more since can have submenus and
        # no way to reach those with below since involves leaving menu
        #self.menu.bind("<Leave>", self.popdown)
        self.menu.bind("<Leave>", self.leave)
        self.menu.bind("<Button-1>", self.buttonPress)
        self.poppedUpSubmenu = False

        self.canvas.bind("<Button-1>", self.canvasPopup)
        self.canvas.bind("<Configure>", self.resizeCallback)

        self.label.grid(row=0, column=0, sticky=Tkinter.W)
        self.canvas.grid(row=0, column=1, sticky=Tkinter.E, padx=2)

    def leave(self, event):

        if (not self.poppedUpSubmenu):
            x = event.x
            y = event.y
            x1 = self.menu.winfo_width()
            y1 = self.menu.winfo_height()

            if (x < 0) or (y < 0) or (x >= x1) or (y >= y1):
                self.popdown()

    def buttonPress(self, event):

        if (event.widget != self.menu):
            return

        for n in range(len(self.entries) - 1, -1, -1):
            if event.y >= self.menu.yposition(n):
                break
        else:
            return

        item = self.entries[n]
        if (isinstance(item, {}.__class__) and item['kind'] == 'cascade'):
            self.poppedUpSubmenu = True
        else:
            self.poppedUpSubmenu = False

    def resizeCallback(self, *event):

        w = self.canvas.winfo_width() / 2
        h = self.canvas.winfo_height() / 2
        self.canvas.delete('all')
        self.canvas.create_polygon(1,
                                   1,
                                   w,
                                   2 * h - 1,
                                   2 * w - 1,
                                   1,
                                   fill=self.fill,
                                   outline=self.outline)

    def getEntry(self, entry_index, entries=None):

        if not entries:
            entries = self.entries

        if type(entry_index) == types.TupleType:
            e = entry_index[0]
            if len(entry_index) > 1:
                items = entries[e]['submenu']
                entry = self.getEntry(entry_index[1:], items)
            else:
                entry = entries[e]['label']
        elif (entry_index >= 0):
            entry = entries[entry_index]
        else:
            entry = None

        return entry

    def getEntryText(self, entry_index, entries=None):

        if not entries:
            entries = self.entries

        if type(entry_index) in (types.TupleType, types.ListType):
            e = entry_index[0]
            if len(entry_index) > 1:
                items = entries[e]['submenu']
                text = self.getEntryText(entry_index[1:], items)
            else:
                text = entries[e]['label']
        elif (entry_index != -1):
            #print 'getEntryText', entry_index, entries
            entry = entries[entry_index]
            if (self.text_attr):
                text = getattr(entry, self.text_attr)
            else:
                text = entry
        else:
            text = ''

        # TBD: for now text returned should not be unicode
        text = str(text)
        # below did not work for one person (Chinese characters??) but above did
        ###text = text.encode('utf-8')

        return text

    def setup(self, entries, selected_index, first_pass=True, colors=None):

        #print 'setup', entries, selected_index
        if first_pass is not None:
            self.first_pass = first_pass

        self.clearMenuItems()
        if (entries):
            e = list(entries)
        else:
            e = [No_entries_label]

        self.entries = e
        self.colors = colors or [None] * len(e)

        if type(selected_index) in (types.TupleType, types.ListType):
            if (not selected_index or selected_index[0] >= len(entries)):
                selected_index = -1
        else:
            if (selected_index >= len(entries)):
                selected_index = -1

        if (selected_index == -1):
            selected_index = 0

        self.selected_index = -1  # changed in setSelectedIndex()
        self.setMenuItems()
        self.setSelectedIndex(selected_index)

    def substituteCascadeCallbacks(self, item, selected_index):

        if (item['kind'] == 'cascade'):
            n = 0
            for subitem in item['submenu']:
                self.substituteCascadeCallbacks(subitem, selected_index + [n])
                n = n + 1
        else:
            callback = item.get('command')
            if (callback):
                label = item['label']
                item['command'] = lambda: self.cascadeCallback(
                    callback, label, tuple(selected_index))

    def cascadeCallback(self, callback, label, selected_index):

        self.selected_index = selected_index
        self.setLabel(selected_index)
        callback(selected_index, label)

    def setMenuItems(self):

        self.menu.images = []
        for n in range(len(self.entries)):
            columnbreak = 0
            if n and n % 20 == 0:
                columnbreak = 1
            item = self.entries[n]
            color = None
            if n < len(self.colors):
                color = self.colors[n]

            if isinstance(item, {}.__class__):
                if (item['kind'] == 'cascade'):
                    self.substituteCascadeCallbacks(item, [n])
            else:
                t = (self.indent * n) + self.extra + str(self.getEntryText(n))
                command = lambda n=n: self.setSelectedIndex(n)
                if color:
                    image = self.makeColorTile(color)
                    item = {
                        'kind': 'command',
                        'accelerator': t,
                        'command': command,
                        'image': image,
                        'columnbreak': columnbreak
                    }
                else:
                    item = {
                        'kind': 'command',
                        'label': t,
                        'command': command,
                        'columnbreak': columnbreak
                    }
            self.menu.addMenuItem(item)

    def clearMenuItems(self):

        if self.entries:
            self.menu.delete(0, len(self.entries))

    # below assumes entry_index is integer not tuple
    def insert(self, entry_index, entry, make_selected=False, color=None):

        if (self.haveNoEntries()):
            if (entry_index != 0):
                return
        elif ((entry_index < 0) or (entry_index >= len(self.entries))):
            return

        self.clearMenuItems()
        self.checkForNone()
        self.entries.insert(entry_index, entry)
        self.colors.insert(entry_index, color)
        self.setMenuItems()

        if (make_selected or (self.selected_index == -1)):
            self.setSelectedIndex(entry_index, force_callback=True)
        else:
            self.setSelectedIndex(self.selected_index)

    # below assumes entry is integer not tuple
    def append(self, entry, make_selected=False, color=None):

        self.clearMenuItems()
        self.checkForNone()
        self.entries.append(entry)
        self.colors.append(color)
        self.setMenuItems()

        if (make_selected or (self.selected_index == -1)):
            self.setSelectedIndex(len(self.entries) - 1, force_callback=True)
        else:
            self.setSelectedIndex(self.selected_index)

    def checkForNone(self):

        if (self.haveNoEntries()):
            self.entries = []
            self.colors = []
            self.menu.delete(0)

    def haveNoEntries(self):

        if (len(self.entries) == 1 and self.entries[0] == No_entries_label):
            return True
        else:
            return False

    def baseIndex(self, ind):

        if type(ind) == types.TupleType:
            ind = ind[0]

        return ind

    # below assumes entry_index is integer not tuple
    def delete(self, entry_index, n=1):

        if (self.haveNoEntries()):
            return

        if ((entry_index < 0) or (entry_index >= len(self.entries))):
            return

        self.clearMenuItems()

        n1 = entry_index
        n2 = n1 + n

        m = self.baseIndex(self.selected_index)

        del self.entries[n1:n2]
        del self.colors[n1:n2]

        if (not self.entries):
            self.entries = [No_entries_label]
            self.colors = [None]

        self.setMenuItems()

        if ((m >= n1) and (m < n2)):
            if (n1 == 0):
                selected_index = 0
            else:
                selected_index = n1 - 1
            self.setSelectedIndex(selected_index, force_callback=True)

    def replace(self, entries, selected_index=-1, colors=None):

        self.menu.delete(0, len(self.entries))
        self.setup(entries, selected_index, first_pass=False, colors=colors)

    def popdown(self, *event):

        self.menu.unpost()

    def setLabel(self, selected_index):

        text = self.getEntryText(selected_index)
        if (not text):
            text = self.extra  # arbitrary

        self.label.set(text=text)

    def labelPopup(self, event):

        self.poppedUpSubmenu = False

        s = self.baseIndex(self.selected_index)
        x = event.x_root - event.x + 2
        y = event.y_root - event.y - \
            max(0, s) * (self.label.winfo_height() + 1)
        self.menu.post(x, y)

    def canvasPopup(self, event):

        s = self.baseIndex(self.selected_index)
        x = event.x_root - event.x - self.label.winfo_width() + 2
        y = event.y_root - event.y - \
            max(0, s) * (self.label.winfo_height() + 1) + \
            self.canvas.winfo_height() - self.label.winfo_height()
        self.menu.post(x, y)

    def getSelectedIndex(self):

        return self.selected_index

    def setSelectedIndex(self, selected_index, force_callback=False):

        if (force_callback or self.force_callback or \
            (selected_index != self.selected_index)):

            #print 'setSelectedIndex', self.entries, selected_index, self.selected_index, self.first_pass
            self.selected_index = selected_index
            self.setLabel(selected_index)

            if (self.callback):
                if (self.haveNoEntries()):
                    if (not self.first_pass):
                        self.callback(-1, None)
                elif (selected_index != -1):
                    if (not self.first_pass or self.do_initial_callback):
                        self.callback(selected_index,
                                      self.entries[selected_index])

        self.first_pass = False

    def get(self):

        return self.getSelected()

    def getSelected(self):

        ind = self.getSelectedIndex()

        if (ind == -1):
            return None
        else:
            return self.getEntry(ind)

    def set(self, selected):

        self.setSelected(selected)

    def findEntryIndex(self, entry, entries=None):

        #print 'findEntryIndex1', entry, entries
        if (entries):
            noEntries = True
        else:
            noEntries = False
            entries = self.entries
            try:
                ind = entries.index(entry)
                return ind
            except:
                pass

        #print 'findEntryIndex2', entry, entries
        for n in range(len(entries)):
            e = entries[n]
            #print 'findEntryIndex3', entry, e
            if e == entry:
                if noEntries:
                    return [n]
                else:
                    return n
            elif type(e) == types.DictType:
                if (len(entries) > 1):
                    try:
                        ind = self.findEntryIndex(entry, e['submenu'])
                        return [n] + ind
                    except:
                        pass
                else:
                    if (entry == e['label']):
                        return [n]

        raise 'unknown entry "%s"' % entry

    # selects first item found in entries which matches
    def setSelected(self, selected):

        #print 'setSelected1', selected, type(selected), self.entries
        try:
            selected_index = self.findEntryIndex(selected)
        except:
            #selected_index = -1
            return

        #print 'setSelected2', selected_index
        self.setSelectedIndex(selected_index)

    def makeColorTile(self, color):

        image = Tkinter.PhotoImage()
        self.menu.images.append(image)

        if type(color) == type([]):
            colors = [scaleColor(self.menu, c, 1.0) for c in color]
        else:
            colors = [
                scaleColor(self.menu, color, 1.0),
            ]

        cols = max(8, len(colors))

        for x in range(cols):
            i = x % len(colors)
            c = colors[i]

            for y in range(16):
                image.put('{%s %s}' % (c, c), to=(2 * x, y))

        return image
Exemple #2
0
class LinkChart(Frame):
    def __init__(self, parent, scrollX=True, scrollY=True, *args, **kw):

        self.parent = parent
        self.scrollX = scrollX
        self.scrollY = scrollY

        self.prototypes = []
        self.nodes = []
        self.node = None  # Selected
        self.links = []
        self.link = None  # Selected
        self.objDict = {}  # Map canvas to nodes, links
        self.cornerDict = {}
        self.editWidget = None
        self.mouseOver = None
        self.inMotion = None
        self.inMotion2 = None
        self.inMotion3 = None
        self.dragRegion = None
        self._resizeBusy = False

        self.selectedNodes = []
        self.selectedLinks = []
        self.highlightNodes = []
        self.highlightLinks = []

        kw['relief'] = 'flat'
        kw['borderwidth'] = 0

        Frame.__init__(self, parent, *args, **kw)

        self.canvas = canvas = Canvas(self, relief='flat', borderwidth=0)
        self.canvas.configure(xscrollincrement=2,
                              yscrollincrement=2,
                              bg=self.cget('bg'))
        self.canvas.grid(row=0, column=0, sticky='nsew')

        if scrollX:
            self.horizScrollbar = Tkinter.Scrollbar(self,
                                                    bg=self.cget('bg'),
                                                    command=self.canvas.xview,
                                                    orient=Tkinter.HORIZONTAL,
                                                    borderwidth=1)
            self.canvas.configure(xscrollcommand=self.horizScrollbar.set)

        if scrollY:
            self.vertScrollbar = Tkinter.Scrollbar(self,
                                                   bg=self.cget('bg'),
                                                   command=self.canvas.yview,
                                                   orient=Tkinter.VERTICAL,
                                                   borderwidth=1)
            self.canvas.configure(yscrollcommand=self.vertScrollbar.set)

        canvas.bind('<Button-1>', self._mouseSingleClick)
        if not isWindowsOS():
            canvas.bind('<Button-4>', self.scrollUp)
            canvas.bind('<Button-5>', self.scrollDown)
        else:
            canvas.bind('<MouseWheel>', self._windowsOsScroll)

        canvas.bind('<Double-1>', self._mouseDoubleClick)
        canvas.bind('<B1-Motion>', self._mouseDrag)
        canvas.bind('<ButtonRelease-1>', self._mouseRelease)
        canvas.bind('<Motion>', self._mouseEnter)
        canvas.bind('<Enter>', self._mouseEnter)
        canvas.bind('<Leave>', self._mouseLeave)
        canvas.bind('<KeyPress-Up>', self._moveUp)
        canvas.bind('<KeyPress-Down>', self._moveDown)
        canvas.bind('<KeyPress-Right>', self._moveRight)
        canvas.bind('<KeyPress-Left>', self._moveLeft)
        canvas.bind('<KeyPress-Delete>', self.deleteSelected)
        self.bind('<Configure>', self._resizeAfter)

        self.menu = Menu(self, tearoff=False)

        alignItems = [{
            'kind': 'command',
            'label': 'Y axis',
            'command': self._alignHorizontal
        }, {
            'kind': 'command',
            'label': 'X axis',
            'command': self._alignVertical
        }]

        distributeItems = [{
            'kind': 'command',
            'label': 'Horizontal',
            'command': self._distributeHorizontal
        }, {
            'kind': 'command',
            'label': 'Verical',
            'command': self._distributeVertical
        }]

        menuItems = [
            {
                'kind': 'cascade',
                'label': 'Align',
                'submenu': alignItems
            },  #0
            {
                'kind': 'cascade',
                'label': 'Distribute',
                'submenu': distributeItems
            },  #1
            {
                'kind': 'command',
                'label': 'Link Selected',
                'command': self.linkSelected
            },  #2
            {
                'kind': 'command',
                'label': 'Reset Links',
                'command': self._resetLinks
            },  #3
            {
                'kind': 'separator'
            },
            {
                'kind': 'command',
                'label': 'Delete Links',  #5
                'command': self.deleteLinks
            },
            {
                'kind': 'command',
                'label': 'Delete Items',  #6
                'command': self.deleteNodes
            },
            {
                'kind': 'separator'
            },
            {
                'kind': 'command',
                'label': 'Save PostScript File',
                'command': self.printCanvas
            },
        ]

        self.menuItems = menuItems
        self.menu.setMenuItems(menuItems)

        canvas.bind('<ButtonPress-3>', self._popupMenu)

        self._drawAfter()

    def _resizeAfter(self, event):

        if self._resizeBusy:
            return

        self._resizeBusy = True
        self._popdownMenu(event)
        self.after_idle(lambda: self._refreshScrollbars(event))

    def _refreshScrollbars(self, event):

        bbox = self.canvas.bbox('all')

        if not bbox:
            self._resizeBusy = False
            return

        cWidth = event.width
        cHeight = event.height

        vertScrollbar = self.vertScrollbar
        horizScrollbar = self.horizScrollbar

        xSpace = bbox[2] + bbox[0]  # Symmetric space
        ySpace = bbox[3] + bbox[1]

        horizHeight = int(horizScrollbar.winfo_height())
        vertWidth = int(vertScrollbar.winfo_width())

        if cHeight < ySpace + horizHeight:
            vertScrollbar.grid(row=0, column=1, sticky='ns')
            cWidth -= vertWidth
        else:
            vertScrollbar.grid_forget()

        if cWidth < xSpace + vertWidth:
            horizScrollbar.grid(row=1, column=0, sticky='ew')
            cHeight -= horizHeight
        else:
            horizScrollbar.grid_forget()

        x1 = 0
        y1 = 0
        x2 = max(cWidth, xSpace)
        y2 = max(cHeight, ySpace)

        self.canvas.configure(width=cWidth, height=cHeight)
        self.canvas.configure(scrollregion=(x1, y1, x2, y2))
        self.update_idletasks()
        self._resizeBusy = False

    def _popupMenu(self, event):

        self._finishEdit()
        self._configMenu()
        self.menu.popupMenu(event)

    def _popdownMenu(self, event):

        self._finishEdit()
        self.menu.unpost()

    def _configMenu(self):

        if self.selectedLinks:
            self.menu.entryconfig(5, state=ACTIVE)
            self.menu.entryconfig(3, state=ACTIVE)
        else:
            self.menu.entryconfig(5, state=INACTIVE)
            self.menu.entryconfig(3, state=INACTIVE)

        if self.selectedNodes:
            self.menu.entryconfig(2, state=ACTIVE)
            self.menu.entryconfig(6, state=ACTIVE)
        else:
            self.menu.entryconfig(2, state=INACTIVE)
            self.menu.entryconfig(6, state=INACTIVE)

        if self.selectedNodes or self.selectedLinks:
            self.menu.entryconfig(0, state=ACTIVE)
            self.menu.entryconfig(1, state=ACTIVE)
        else:
            self.menu.entryconfig(0, state=INACTIVE)
            self.menu.entryconfig(1, state=INACTIVE)

    def _resetLinks(self):

        for link in self.selectedLinks:
            link.resetCoords()

    def _alignSelected(self, vertical=False, horizontal=False):

        obj = self.node or self.link

        if not obj:
            return

        x0, y0 = obj.coords

        objs = self.selectedNodes + self.selectedLinks
        n = len(objs)

        if n < 2:
            return

        if horizontal:
            for obj in objs:
                obj.move(obj.coords[0], y0)

        if vertical:
            for obj in objs:
                obj.move(x0, obj.coords[1])

    def _distributeSelected(self, vertical=False, horizontal=False):

        xCoords = []
        yCoords = []

        objs = self.selectedNodes + self.selectedLinks
        n = len(objs)

        if n < 3:
            return

        for obj in objs:
            x, y = obj.coords
            xCoords.append((x, obj))
            yCoords.append((y, obj))

        if horizontal:
            xCoords.sort()
            xA = xCoords[0][0]
            dx = (xCoords[-1][0] - xA) / float(n - 1)

            for i, (x0, obj) in enumerate(xCoords):
                y = obj.coords[1]
                x = xA + (i * dx)
                obj.move(x, y)

        if vertical:
            yCoords.sort()
            yA = yCoords[0][0]
            dy = (yCoords[-1][0] - yA) / float(n - 1)

            for i, (y0, obj) in enumerate(yCoords):
                x = obj.coords[0]
                y = yA + (i * dy)
                obj.move(x, y)

    def _alignHorizontal(self):

        self._alignSelected(horizontal=True)

    def _alignVertical(self):

        self._alignSelected(vertical=True)

    def _distributeHorizontal(self):

        self._distributeSelected(horizontal=True)

    def _distributeVertical(self):

        self._distributeSelected(vertical=True)

    def linkSelected(self):

        nodes = self.selectedNodes
        n = len(nodes)
        if n > 1:

            for i in range(n - 1):
                nodeA = nodes[i]

                for j in range(i + 1, n):
                    nodeB = nodes[j]

                    for link in nodeA.links:
                        if nodeB in link.nodes:
                            #print link, link.nodes, nodeA, nodeB
                            break

                    else:
                        link = Link(self, nodeA, nodeB, text='*')
                        #print link

    def deleteNodes(self):

        if self.selectedNodes:
            msg = 'Really delete %d selected nodes?' % len(self.selectedNodes)
            if showOkCancel('Confirm', msg, parent=self):
                for node in self.selectedNodes[:]:
                    node.delete()
                self.selectedNodes = []
                self.node = None

    def deleteLinks(self):

        if self.selectedLinks:
            msg = 'Really delete %d selected links?' % len(self.selectedLinks)
            if showOkCancel('Confirm', msg, parent=self):
                for link in self.selectedLinks[:]:
                    link.delete()

                self.selectedLinks = []
                self.link = None

    def deleteSelected(self, event=None):

        objs = self.selectedLinks + self.selectedNodes
        if objs:
            n1 = len(self.selectedNodes)
            n2 = len(self.selectedLinks)
            msg = 'Really delete %d selected objects?\n' % (n1 + n2)
            msg += '(%d nodes, %d links)' % (n1, n2)
            if showOkCancel('Confirm', msg, parent=self):
                for obj in objs:
                    obj.delete()

                self.selectedLinks = []
                self.link = None
                self.selectedNodes = []
                self.node = None

    def printCanvas(self, *event):

        from memops.gui.FileSelect import FileType
        from memops.gui.FileSelectPopup import FileSelectPopup

        fileTypes = [FileType('PostScript', ['*.ps']), FileType('All', ['*'])]
        fileSelectPopup = FileSelectPopup(self,
                                          file_types=fileTypes,
                                          title='Print canvas to file',
                                          dismiss_text='Cancel',
                                          selected_file_must_exist=False)

        fileName = fileSelectPopup.getFile()

        self.postscript(colormode='color', file=fileName)

    def _clearSelection(self):

        for node in self.selectedNodes:
            node.isSelected = False

        for link in self.selectedLinks:
            link.isSelected = False

        self.selectedNodes = []
        self.selectedLinks = []
        self.link = None
        self.node = None

    def _clearHighlights(self):

        for node in self.highlightNodes:
            node.highlight = False

        for link in self.highlightLinks:
            link.highlight = False

        self.mouseOver = None
        self.highlightNodes = []
        self.highlightLinks = []

    def _mouseSingleClick(self, event):

        self._mouseClick(event)

    def _mouseDoubleClick(self, event):

        self._mouseClick(event, True)

    def _mouseClick(self, event, edit=False):

        #self.menu.popdownMenu(event)

        self._finishEdit()

        canvas = event.widget
        canvas.focus_force()

        x = canvas.canvasx(event.x)
        y = canvas.canvasy(event.y)
        items = canvas.find('overlapping', x - 1, y - 1, x + 1, y + 1)

        #item = self.find('closest',x,y)[0]
        if not items:
            self._clearSelection()
            self._clearHighlights()
            self.draw()
            return

        objs = [
            self.objDict.get(item) for item in items if self.objDict.get(item)
        ]

        for obj in objs:
            if isinstance(obj, Node):
                if not (event.state & 4 or event.state & 1):
                    self._clearSelection()

                obj.isSelected = True
                self.node = obj
                if obj not in self.selectedNodes:
                    self.selectedNodes.append(obj)

                break

        else:
            self.node = None

            for obj in objs:
                if isinstance(obj, Link):
                    if not (event.state & 4 or event.state & 1):
                        self._clearSelection()

                    obj.isSelected = True
                    self.link = obj

                    if obj not in self.selectedLinks:
                        self.selectedLinks.append(obj)

                    break

            else:
                self.link = None

        self._drawAfter()
        if not obj:
            return

        if edit and obj.editWidget:
            obj.edit()
            self.editWidget = obj.editWidget

    def _clearDragRegion(self, canvas):

        if self.dragRegion:
            canvas.delete(self.dragRegion[0])
            self.dragRegion = None

    def _mouseEnter(self, event):

        self.menu.popdownMenu(event)
        canvas = event.widget
        canvas.focus_force()

        x = canvas.canvasx(event.x)
        y = canvas.canvasy(event.y)
        items = canvas.find('overlapping', x - 1, y - 1, x + 1, y + 1)

        #item = self.find('closest',x,y)[0]
        if not items:
            self._clearHighlights()
            self.draw()
            return

        obj = self.objDict.get(items[0])

        self._clearHighlights()

        if obj is None:
            self.draw()
            return

        if obj is self.mouseOver:
            if self.inMotion2:
                for item in items:
                    if self.cornerDict.has_key(item):
                        self.inMotion2 = None
                        break

            return

        objs = [
            self.objDict.get(item) for item in items if self.objDict.get(item)
        ]

        for obj in objs:
            if isinstance(obj, Node):
                self.highlightNodes = [
                    obj,
                ]
                break

        else:
            for obj in objs:
                if isinstance(obj, Link):
                    self.highlightLinks = [
                        obj,
                    ]

        obj.highlight = True

        self.mouseOver = obj
        self.draw()

    def _mouseLeave(self, event):

        #self._mouseRelease(event)
        #self._finishEdit()
        self._clearHighlights()
        self.draw()

    def _mouseDrag(self, event):

        self.menu.popdownMenu(event)

        canvas = event.widget
        canvas.focus_force()
        x = canvas.canvasx(event.x)
        y = canvas.canvasy(event.y)

        #width = int(canvas.winfo_width())
        #height = int (canvas.winfo_height())
        #
        #if not (0 < x < width) or not (0 < y < height):
        #  self._mouseRelease(event)
        #  return

        inMotion = self.inMotion

        objs = self.selectedNodes + self.selectedLinks
        if objs:
            items = canvas.find('overlapping', x - 1, y - 1, x + 1, y + 1)

            if not inMotion:
                for item in items:
                    if self.inMotion2 or self.cornerDict.has_key(item):
                        obj = self.node

                        x1, y1 = obj.coords  # offsets
                        dx = abs(x - x1)
                        dy = abs(y - y1)

                        size = max(dx, dy) + 1

                        obj.width = size
                        obj.draw()

                        self.inMotion2 = True

                        return

            if inMotion:
                offsets = inMotion  # Get offsets

            else:
                offsets = []
                for obj in objs:
                    x1, y1 = obj.coords  # offsets
                    dx = x - x1  # Offset relative to centre
                    dy = y - y1

                    if isinstance(obj, Node) and obj.isPrototype:
                        obj = obj.copy()

                    offsets.append((dx, dy, obj))

                self.inMotion = offsets  # Store click offset and Node

            #print event, event.widget._root()

            for dx, dy, obj in offsets:
                x2 = x - dx
                y2 = y - dy

                obj.move(x2, y2)

            x0, y0 = event.x_root, event.y_root
            under = self.winfo_containing(x0, y0)

            if isinstance(under, Canvas):
                under = under.parent

            if isinstance(under, LinkChart) and (under is not self):
                for dx, dy, obj in offsets:
                    if not isinstance(obj, Node):
                        continue

                    original = obj.original
                    if original and original.isPrototype:
                        xT = x0 - under.winfo_rootx() - dx
                        yT = y0 - under.winfo_rooty() - dy

                        if original.spawn:
                            original.spawn.move(xT, yT)
                        else:
                            original.spawn = original.copy(under,
                                                           coords=(xT, yT))

            else:
                for dx, dy, obj in offsets:
                    if not isinstance(obj, Node):
                        continue

                    original = obj.original
                    if original and original.spawn:
                        original.spawn.delete()
                        original.spawn = None

        else:
            if self.inMotion3:
                x0, y0, = self.inMotion3

            else:
                #self._mouseRelease(event)
                x0 = x
                y0 = y
                self.inMotion3 = (x, y)

            if self.dragRegion:
                item = self.dragRegion[0]
                canvas.coords(item, x0, y0, x, y)
            else:
                item = canvas.create_rectangle(x0,
                                               y0,
                                               x,
                                               y,
                                               outline='#008000',
                                               fill='#00E000',
                                               stipple='gray25')

            self.dragRegion = (item, x0, y0, x, y)

    def _mouseRelease(self, event):

        canvas = event.widget

        if self.dragRegion:
            item, x1, y1, x2, y2 = self.dragRegion

            xCoords = [x1, x2]
            yCoords = [y1, y2]
            xCoords.sort()
            yCoords.sort()

            x1, x2 = xCoords
            y1, y2 = yCoords

            self.node = None
            self.selectedNodes = []
            for node in self.nodes:
                x0, y0 = node.coords

                if (x1 < x0 < x2) and (y1 < y0 < y2):
                    self.selectedNodes.append(node)
                    node.isSelected = True
                    self.node = node
                else:
                    node.isSelected = False

            self.link = None
            self.selectedLinks = []
            for link in self.links:
                x0, y0 = link.coords

                if (x1 < x0 < x2) and (y1 < y0 < y2):
                    self.selectedLinks.append(link)
                    link.isSelected = True
                    self.link = link
                else:
                    link.isSelected = False

            self._clearDragRegion(canvas)

            self.draw()

        if self.inMotion:
            for dx, dy, obj in self.inMotion:
                if isinstance(
                        obj,
                        Node) and obj.original and obj.original.isPrototype:
                    if obj.original.spawn:
                        obj.original.spawn.original = None
                        obj.original.spawn = None
                    obj.delete()

        self.inMotion = None
        self.inMotion2 = None
        self.inMotion3 = None

    def _finishEdit(self):

        if self.editWidget:
            self.focus_force()
            self.editWidget.commit()
            self.editWidget.hide()
            self.editWidget = None

    def _moveUp(self, event):

        self._moveSelected(event, 0, -1)

    def _moveDown(self, event):

        self._moveSelected(event, 0, 1)

    def _moveRight(self, event):

        self._moveSelected(event, 1, 0)

    def _moveLeft(self, event):

        self._moveSelected(event, -1, 0)

    def _moveSelected(self, event, dx, dy):

        if not (event.state & 4 or event.state & 1):
            dx *= MOVE_STEP
            dy *= MOVE_STEP

        for node in self.selectedNodes:
            x0, y0 = node.coords
            node.move(x0 + dx, y0 + dy)

        for link in self.selectedLinks:
            x0, y0 = link.coords
            link.move(x0 + dx, y0 + dy)

    def scrollUp(self, event=None):

        pass

    def scrollDown(self, event):

        pass

    def _windowsOsScroll(self, event=None):

        delta = event.delta

        if delta > 0:
            self.scrollUp(event)

        elif delta < 0:
            self.scrollDown(event)

    def _drawAfter(self):

        self.after_idle(self.draw)

    def draw(self):

        for link in self.links:
            link.draw()

        for node in self.nodes:
            node.draw()
Exemple #3
0
class ScrolledCanvas(Frame):
    def __init__(self,
                 parent,
                 resizeCallback=None,
                 width=600,
                 height=600,
                 *args,
                 **kw):

        self.bbox = None
        self.busy = 0
        self.initialX = None
        self.initialY = None
        self.resizeCallback = resizeCallback

        apply(Frame.__init__, (self, parent) + args, kw)

        self.grid_rowconfigure(0, weight=1)
        self.grid_columnconfigure(0, weight=1)

        self.menu = Menu(self, tearoff=0, include_event=True)
        self.configMenu()

        self.canvas = Canvas(self,
                             relief='flat',
                             borderwidth=0,
                             width=width,
                             height=height)
        self.canvas.configure(xscrollincrement=2, yscrollincrement=2)
        self.canvas.grid(row=0, column=0, sticky=Tkinter.NSEW)

        self.horizScrollbar = Tkinter.Scrollbar(self,
                                                bg=self.cget('bg'),
                                                command=self.canvas.xview,
                                                orient=Tkinter.HORIZONTAL,
                                                borderwidth=1)
        self.vertScrollbar = Tkinter.Scrollbar(self,
                                               bg=self.cget('bg'),
                                               command=self.canvas.yview,
                                               orient=Tkinter.VERTICAL,
                                               borderwidth=1)
        self.canvas.configure(xscrollcommand=self.horizScrollbar.set,
                              yscrollcommand=self.vertScrollbar.set)

        self.canvas.bind('<Configure>', self.resizeAfter)
        self.canvas.bind('<Button-1>', self.mouseButton1)
        self.canvas.bind('<Button-2>', self.mouseButton2)
        self.canvas.bind('<Button-3>', self.mouseButton3)
        self.canvas.bind('<ButtonRelease-1>', self.mouseButtonRelease1)
        self.canvas.bind('<ButtonRelease-2>', self.mouseButtonRelease2)
        self.canvas.bind('<B2-Motion>', self.mouseScroll)
        self.canvas.bind('<B3-Motion>', self.doNothing)
        self.canvas.bind('<Motion>', self.mouseEnter)
        self.canvas.bind('<Enter>', self.mouseEnter)

    def doNothing(self, event):

        pass

    def mouseEnter(self, event):

        if self.menu.winfo_ismapped():
            self.removeMenu()

    def refresh(self):

        self.bbox = self.canvas.bbox('all')
        bbox = self.bbox

        if not bbox:
            self.busy = 0
            return

        cWidth = int(self.canvas.cget('width'))
        cHeight = int(self.canvas.cget('height'))

        if cHeight > bbox[3] - bbox[1]:
            self.vertScrollbar.grid_forget()
        else:
            self.vertScrollbar.grid(row=0, column=1, sticky=Tkinter.NS)

        if cWidth > bbox[2] - bbox[0]:
            self.horizScrollbar.grid_forget()
        else:
            self.horizScrollbar.grid(row=1, column=0, sticky=Tkinter.EW)

        x1 = bbox[0]
        y1 = bbox[1]
        x2 = max(bbox[0] + cWidth, bbox[2])
        y2 = max(bbox[1] + cHeight, bbox[3])

        self.canvas.configure(scrollregion=(x1, y1, x2, y2))
        self.update_idletasks()
        self.busy = 0

    def printCanvas(self, *event):

        fileTypes = [FileType('PostScript', ['*.ps']), FileType('All', ['*'])]
        fileSelectPopup = FileSelectPopup(self,
                                          file_types=fileTypes,
                                          title='Print canvas to file',
                                          dismiss_text='Cancel',
                                          selected_file_must_exist=False)

        fileName = fileSelectPopup.getFile()

        self.bbox = bbox = self.canvas.bbox('all')
        w = bbox[2] - bbox[0]
        h = bbox[3] - bbox[1]
        self.canvas.postscript(colormode='color',
                               file=fileName,
                               x=bbox[0],
                               y=bbox[1],
                               width=w + 2,
                               pagewidth='21.c',
                               height=h + 2)

    def configMenu(self):

        items = [
            {
                'kind': 'command',
                'label': 'Print to file',
                'command': self.printCanvas
            },
        ]

        self.menu.setMenuItems(items)

    def removeMenu(self, *event):

        self.menu.unpost()

    def mouseButton3(self, event):

        self.menu.popupMenu(event)

    def mouseButton1(self, event):

        self.removeMenu()

        if not self.initialX:
            self.initialX = event.x
            self.initialY = event.y

    def mouseButton2(self, event):

        self.removeMenu()

        if not self.initialX:
            self.initialX = event.x
            self.initialY = event.y
            self.initialPX = self.horizScrollbar.get()[0]
            self.initialPY = self.vertScrollbar.get()[0]

    def mouseButtonRelease1(self, event):

        self.initialX = None
        self.initialY = None

    def mouseButtonRelease2(self, event):

        self.initialX = None
        self.initialY = None
        self.initialPX = None
        self.initialPY = None

    def mouseScroll(self, event):

        self.menu.unpost()
        bbox = self.bbox
        if not bbox:
            return
        bW = float(bbox[2] - bbox[0])
        bH = float(bbox[3] - bbox[1])
        cWidth = int(self.canvas.cget('width'))
        cHeight = int(self.canvas.cget('height'))

        if cWidth < bW:
            dx = self.initialX - event.x
            prop = self.initialPX + (dx / bW)
            self.canvas.xview('moveto', prop)

        if cHeight < bH:
            dy = self.initialY - event.y
            prop = self.initialPY + (dy / bH)
            self.canvas.yview('moveto', prop)

    def resizeAfter(self, event):

        if self.busy:
            return
        else:
            self.busy = 1
            self.removeMenu()
            self.after_idle(lambda: self.resize(event))

    def resize(self, event):

        if self.resizeCallback:
            self.after_idle(
                lambda: self.resizeCallback(event.width, event.height))
        self.canvas.configure(width=event.width, height=event.height)
        self.after_idle(self.refresh)
Exemple #4
0
class PulldownList(Frame):

    # if using indentation then should use list as stack only
    # in other words only delete or insert at end

    # There is a 1:1 correspondance between the ordered lists of texts and
    # list of objects

    # categories is the category name for each text/object
    # objects with the same category will go under a submenu
    # of that name

    def __init__(self,
                 parent,
                 callback=None,
                 texts=None,
                 objects=None,
                 categories=None,
                 colors=None,
                 index=0,
                 prefix='',
                 indent='',
                 initCallback=False,
                 forceCallback=False,
                 numbering=False,
                 arrowLine='#602000',
                 arrowFill='#B05848',
                 labelColor='#501000',
                 menuBg='#F0F0FF',
                 sticky='w',
                 *args,
                 **kw):

        Frame.__init__(self, parent, sticky=sticky, *args, **kw)

        self.callback = callback
        self.texts = texts or []
        self.objects = objects or []
        self.categories = categories or []
        self.colors = colors or []
        self.prefix = prefix
        self.indent = indent
        self.initCallback = initCallback
        self.numbering = numbering
        self.arrowLine = arrowLine
        self.arrowFill = arrowFill
        self.labelColor = labelColor

        # Current selection
        self.index = None
        self.object = NullText

        self.rows = []
        self.bg = self.cget('bg')
        self.label = Label(self, foreground=labelColor)
        self.canvas = Canvas(self, width=12, height=12, background=self.bg)
        self.menu = Menu(self.canvas,
                         tearoff=False,
                         bg=menuBg,
                         relief='solid',
                         borderwidth=1,
                         activeborderwidth=1)

        self.menu.images = []  # Photoimage has to remain referenced

        self.setup(self.texts, self.objects, index, self.colors,
                   self.categories)

        self.label.bind("<Button-1>", self._labelClick)
        self.menu.bind("<Leave>", self._leave)
        self.canvas.bind("<Button-1>", self._canvasClick)
        self.canvas.bind("<Configure>", self._resizeCallback)

        self.grid_columnconfigure(0, weight=1)
        self.label.grid(row=0, column=0, sticky='w')
        self.canvas.grid(row=0, column=1, sticky='w', padx=2)

    #
    # Retrieval
    #

    def get(self):

        return (self.getText(), self.getObject())

    def getSelected(self):

        return self.get()

    def getObject(self):

        return self._fetch(self.index, self.objects)

    def getText(self):

        return self._fetch(self.index, self.texts)

    def getSelectedIndex(self):

        return self.index

    #
    # Setting selected
    #

    def set(self, item):
        # Works with an object or a text

        index = None

        if item in self.texts:
            index = list(self.texts).index(item)

        elif item in self.objects:
            index = list(self.objects).index(item)

        if index is not None:
            self.setIndex(index)

    def setSelected(self, item):

        self.set(item)

    def setIndex(self, index, doCallback=False):

        self.index = index

        if self.objects:
            obj = self.objects[index]

            if obj is not self.object:
                self.object = obj

                if (doCallback
                        or self.initCallback) and self.texts and self.callback:
                    self.callback(obj)

        self._updateLabel()

    #
    # Bulk configuration
    #

    def clear(self):

        self.setup([], [], 0)

    def setup(self, texts, objects, index, colors=None, categories=None):

        self.texts = texts
        nTexts = len(texts)

        if not objects:
            objects = texts

        while len(objects) < nTexts:
            objects.append(None)

        self.objects = objects

        if colors is None:
            self.colors = [None] * nTexts
        else:
            while len(colors) < nTexts:
                colors.append(None)
            self.colors = colors

        if categories is None:
            self.categories = [None] * nTexts

        else:
            while len(categories) < nTexts:
                categories.append(None)
            self.categories = categories

        self._setMenuItems()

        self.setIndex(index or 0)

    #
    # In-place/minor configuration
    #

    def insert(self,
               index,
               text,
               object=None,
               color=None,
               category=None,
               select=False):

        index = max(0, min(len(self.texts), index))

        self.texts.insert(index, text)
        self.objects.insert(index, object)
        self.colors.insert(index, color)
        self.categories.insert(index, category)

        self._setMenuItems()

        if select:
            self.setIndex(index)

    def append(self,
               text,
               object=None,
               color=None,
               category=None,
               select=False):

        self.insert(len(self.texts),
                    text,
                    object=None,
                    color=None,
                    category=None,
                    select=False)

    def delete(self, index, howMany=1):

        if index < 0:
            return
        elif index >= len(self.texts):
            return

        end = min(index + howMany, len(self.texts))

        self._clearMenu()

        del self.texts[index:end]
        del self.objects[index:end]
        del self.colors[index:end]
        del self.categories[index:end]

        self._setMenuItems()

        index = min(len(self.texts) - 1, self.index)

        self.setIndex(index)

    #
    # Internal methods
    #

    def _leave(self, event):

        x = event.x
        y = event.y
        x1 = self.menu.winfo_width()
        y1 = self.menu.winfo_height()

        if (x < 0) or (y < 0) or (x >= x1) or (y >= y1):
            self._popdown()

    def _resizeCallback(self, *event):

        c = self.canvas
        w = c.winfo_width() - 1
        h = c.winfo_height() - 1
        c.delete('all')
        c.create_rectangle(0,
                           0,
                           10,
                           2,
                           fill=self.arrowFill,
                           outline=self.arrowLine)
        c.create_polygon(0,
                         4,
                         0,
                         6,
                         5,
                         11,
                         10,
                         6,
                         10,
                         4,
                         fill=self.arrowFill,
                         outline=self.arrowLine)

    def _fetch(self, index, array):

        if index is None:
            return None

        if index < 0:
            index += len(array)

        if index < 0:
            return None

        elif index >= len(array):
            return None

        else:
            return array[index]

    def _setMenuItems(self):
        self._clearMenu()
        self.menu.images = []  # Clear photoimages

        if not self.texts and not self.menu.entrycget(1, 'label'):

            item = {'kind': 'command', 'label': NullText, 'command': None}
            self.menu.addMenuItem(item)
            self.rows = [0]

            return

        topList = []
        categoryDict = {}

        for i in range(len(self.texts)):
            text = self.texts[i]
            color = self.colors[i]
            category = self.categories[i]

            if category:
                if categoryDict.get(category) is None:
                    categoryDict[category] = []
                    topList.append((None, category, None, category))

                categoryDict[category].append((i, text, color))

            else:
                topList.append((i, text, color, None))

        row = 0
        for index, text, color, cat in topList:
            columnbreak = 0
            if row and row % 20 == 0:
                columnbreak = 1

            if cat:
                string = (self.indent * row) + self.prefix + text
                items = []

                rowB = 0
                for index2, text2, color2 in categoryDict.get(cat, []):
                    columnbreakB = 0
                    if rowB and rowB % 20 == 0:
                        columnbreakB = 1

                    if self.numbering:
                        number = '%d%. ' % (index2 + 1)
                    else:
                        number = ''

                    string2 = number + self.prefix + text2
                    command = lambda n=index2: self.setIndex(n, True)
                    if color2:
                        image = self._makeColorTile(color2)
                        item2 = {
                            'kind': 'command',
                            'accelerator': string2,
                            'command': command,
                            'image': image,
                            'columnbreak': columnbreakB
                        }
                    else:
                        item2 = {
                            'kind': 'command',
                            'label': string2,
                            'command': command,
                            'columnbreak': columnbreakB
                        }

                    items.append(item2)
                    self.rows.append(row)
                    rowB += 1

                item = {
                    'kind': 'cascade',
                    'label': string,
                    'submenu': items,
                    'columnbreak': columnbreak
                }

            else:

                if self.numbering:
                    number = '%d%. ' % (index + 1)
                else:
                    number = ''

                string = (self.indent * row) + number + self.prefix + text
                command = lambda n=index: self.setIndex(n, True)

                if color:
                    image = self._makeColorTile(color)
                    item = {
                        'kind': 'command',
                        'accelerator': string,
                        'command': command,
                        'image': image,
                        'columnbreak': columnbreak
                    }
                else:
                    item = {
                        'kind': 'command',
                        'label': string,
                        'command': command,
                        'columnbreak': columnbreak
                    }

            self.menu.addMenuItem(item)
            self.rows.append(row)

            row += 1

    def _clearMenu(self):

        self.menu.delete(0, 'end')
        self.index = 0
        self.rows = []

    def _popdown(self, *event):

        self.menu.unpost()

    def _labelClick(self, event):

        s = self.rows[self.index]
        #x = event.x_root - event.x + 2 + self.label.winfo_width()
        x = event.x_root - 2
        y = event.y_root - event.y - max(0,
                                         s) * (self.label.winfo_height() + 1)
        self.menu.post(x, y)

    def _canvasClick(self, event):

        s = self.rows[self.index]
        x = event.x_root - event.x + 2
        y = event.y_root - event.y - max(0,
                                         s) * (self.label.winfo_height() + 1)
        self.menu.post(x, y)

    def _updateLabel(self):

        if self.texts:
            text = self.texts[self.index] or NullText
        else:
            text = NullText

        self.label.set(text=text)

    def _makeColorTile(self, color):

        image = Tkinter.PhotoImage()
        self.menu.images.append(image)

        if type(color) == type([]):
            colors = [scaleColor(self.menu, c, 1.0) for c in color]
        else:
            colors = [
                scaleColor(self.menu, color, 1.0),
            ]

        cols = max(8, len(colors))

        for x in range(cols):
            i = x % len(colors)
            c = colors[i]

            for y in range(16):
                image.put('{%s %s}' % (c, c), to=(2 * x, y))

        return image