Logo Search packages:      
Sourcecode: whyteboard version File versions  Download package

panels.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-

# Copyright (c) 2009, 2010 by Steven Sproat
#
# GNU General Public Licence (GPL)
#
# Whyteboard is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation; either version 3 of the License, or (at your option) any later
# version.
# Whyteboard is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
# details.
# You should have received a copy of the GNU General Public License along with
# Whyteboard; if not, write to the Free Software Foundation, Inc., 59 Temple
# Place, Suite 330, Boston, MA  02111-1307  USA


"""
This module contains classes for the GUI side panels and pop-up menus.
"""

from __future__ import division
import os

import wx
import wx.media
import wx.lib.colourselect as csel

from wx.lib import scrolledpanel as scrolled
from wx.lib.buttons import GenBitmapToggleButton
from wx.lib.wordwrap import wordwrap as wordwrap

from whyteboard.lib import pub

from whyteboard.misc import (meta, create_colour_bitmap, get_time, file_dialog,
                             get_image_path)
from whyteboard.gui import NotesPopup, ThumbsPopup

_ = wx.GetTranslation

#----------------------------------------------------------------------

00046 class ControlPanel(wx.Panel):
    """
    This class implements a control panel for the GUI. It creates buttons or
    icons for each tool that can be drawn with on the Whyteboard; a drop-down
    menu for the line thickness and a ColourPicker for choosing the drawing
    colour. A preview of what the tool will look like is also shown.

    It is contained within a collapsed pane, to give extra screen space when in
    full screen mode
    """
00056     def __init__(self, gui):
        """
        Stores a reference to the drawing preview and the toggled drawing tool.
        """
        wx.Panel.__init__(self, gui, style=0 | wx.RAISED_BORDER)

        cp = wx.CollapsiblePane(self, style=wx.CP_DEFAULT_STYLE | wx.CP_NO_TLW_RESIZE)
        self.pane = cp.GetPane()  # every widget's parent
        self.gui = gui
        self.toggled = 1  # Pen, initallly
        self.tools = {}
        self.thickness_timer = None
        self.thickness_scrolling = False

        sizer = wx.BoxSizer(wx.VERTICAL)
        csizer = wx.BoxSizer(wx.VERTICAL)
        box = wx.BoxSizer(wx.VERTICAL)
        self.grid = wx.GridSizer(cols=3, hgap=4, vgap=4)
        self.toolsizer = wx.GridSizer(cols=1, hgap=5, vgap=5)

        self.make_toolbox(gui.util.config['toolbox'])
        self.make_colour_grid()
        colour = self.colour_buttons()
        thickness = wx.StaticText(self.pane, label=_("Thickness:"))

        choices = u''.join(u"%s " % i for i in range(1, 35)).split()
        self.thickness = wx.ComboBox(self.pane, choices=choices, size=(25, 25),
                                        style=wx.CB_READONLY)

        self.thickness.SetSelection(0)
        self.thickness.SetToolTipString(_("Sets the drawing thickness"))

        line = wx.StaticLine(self, size=(-1, 30))
        line.SetBackgroundColour((0, 0, 0))
        self.preview = DrawingPreview(self.pane, self.gui)
        spacing = 4

        box.AddMany([(self.toolsizer, 0, wx.ALIGN_CENTER | wx.ALL, spacing),
                     ((5, 8)), (self.grid, 0, wx.EXPAND | wx.ALL, spacing),
                     ((5, 8)), (colour, 0, wx.EXPAND | wx.ALL, spacing),
                     ((5, 10)), (thickness, 0, wx.ALL | wx.ALIGN_CENTER, spacing),
                     (self.thickness, 0, wx.EXPAND | wx.ALL, spacing),
                     ((5, 5)), (self.preview, 0, wx.EXPAND | wx.ALL, spacing)])
        csizer.Add(box, 1, wx.EXPAND)
        sizer.Add(cp, 1, wx.EXPAND)

        self.SetSizer(sizer)
        cp.GetPane().SetSizer(csizer)
        cp.Expand()
        self.control_sizer = box
        self.background.Raise()

        if not self.gui.util.config['tool_preview']:
            self.preview.Hide()
        if not self.gui.util.config['colour_grid']:
            box.Hide(self.grid)

        self.Bind(wx.EVT_COLLAPSIBLEPANE_CHANGED, self.toggle)
        #self.thickness.Bind(wx.EVT_MOUSEWHEEL, self.scroll)
        self.thickness.Bind(wx.EVT_COMBOBOX, self.change_thickness)
        pub.subscribe(self.set_colour, 'change_colour')
        pub.subscribe(self.set_background, 'change_background')


    def colour_buttons(self):
        panel = wx.Panel(self.pane)
        self.colour = csel.ColourSelect(panel, pos=(0, 0), size=(60, 60))
        parent = panel
        if os.name == "nt":
            parent = self.colour  # segfaults otherwise

        sizer = wx.BoxSizer()
        swap_sizer = wx.BoxSizer(wx.HORIZONTAL)

        self.background = csel.ColourSelect(parent, pos=(0, 30), size=(30, 30))
        self.background.SetValue("White")
        swap = wx.BitmapButton(panel, bitmap=wx.Bitmap(get_image_path(u"icons", u"swap_colours")),
                               pos=(70, 0), style=wx.NO_BORDER)
        self.transparent = wx.CheckBox(panel, label=_("Transparent"), pos=(0, 69))
        self.transparent.SetValue(True)

        self.colour.Bind(csel.EVT_COLOURSELECT, self.change_colour)
        self.background.Bind(csel.EVT_COLOURSELECT, self.change_background)
        self.transparent.Bind(wx.EVT_CHECKBOX, self.on_transparency)
        swap.Bind(wx.EVT_BUTTON, self.on_swap)

        self.colour.SetToolTipString(_("Set the foreground color"))
        self.background.SetToolTipString(_("Set the background color"))
        self.transparent.SetToolTipString(_("Ignores the background color"))
        swap.SetToolTipString(_("Swaps the foreground and background colors"))

        sizer.AddMany([(self.background), (self.colour), (self.transparent)])
        swap_sizer.Add(sizer)
        swap_sizer.Add(swap, flag=wx.ALIGN_RIGHT)
        return panel


    def get_background_colour(self):
        return self.background.GetColour()

    def get_colour(self):
        return self.colour.GetColour()

    def set_colour(self, colour):
        self.colour.SetColour(colour)
        self.preview.Refresh()

    def set_background(self, colour):
        self.background.SetColour(colour)
        self.preview.Refresh()

00167     def make_toolbox(self, _type=u"text"):
        """Creates a toolbox made from toggleable text or icon buttons"""
        items = [_(i.name) for i in self.gui.util.items]
        self.toolsizer.SetCols(1)

        if _type == u"icon":
            items = [_(i.icon) for i in self.gui.util.items]
            self.toolsizer.SetCols(int(self.gui.util.config['toolbox_columns']))

        for x, val in enumerate(items):
            if _type == u"icon":
                path = get_image_path(u"tools", val)
                b = GenBitmapToggleButton(self.pane, x + 1, wx.Bitmap(path),
                                          style=wx.NO_BORDER)
                evt = wx.EVT_BUTTON
            else:
                b = wx.ToggleButton(self.pane, x + 1, val)
                evt = wx.EVT_TOGGLEBUTTON

            b.SetToolTipString(u"%s\n%s %s" % (_(self.gui.util.items[x].tooltip),
                                            _("Shortcut Key:"),
                                            self.gui.util.items[x].hotkey.upper()))

            b.Bind(evt, self.change_tool, id=x + 1)
            self.toolsizer.Add(b, 0, wx.EXPAND | wx.RIGHT, 2)
            self.tools[x + 1] = b
        self.tools[self.toggled].SetValue(True)


00196     def make_colour_grid(self):
        """Builds a colour grid from the user's preferred colours"""
        colours = []
        for x in range(1, 10):
            col = self.gui.util.config["colour%s" % x]
            colours.append([int(c) for c in col])

        for colour in colours:
            method = lambda evt, col = colour: self.change_colour(evt, col)
            method2 = lambda evt, col = colour: self.change_background(evt, col)

            b = wx.BitmapButton(self.pane, bitmap=create_colour_bitmap(colour))
            self.grid.Add(b, 0)
            b.Bind(wx.EVT_BUTTON, method)
            b.Bind(wx.EVT_RIGHT_UP, method2)


00213     def toggle(self, evt):
        """Toggles the collapsible pane and its widgets"""
        self.gui.Layout()
        self.gui.canvas.redraw_all()  # fixes a windows redraw bug


    def toggle_colour_grid(self, value):
        self.control_sizer.Show(self.grid, value)
        self.pane.Layout()

00223     def scroll(self, event):
        """Scrolls the thickness drop-down box (for Windows)"""
        val = self.thickness.GetSelection()
        if event.GetWheelRotation() > 0:  # mousewheel down
            val -= 1
            if val <= 0:
                val = 0
        else:
            val += 1

        self.thickness.SetSelection(val)


00236     def change_tool(self, event=None, _id=None):
        """
        Toggles the tool buttons on/off
        """
        new = self.gui.util.tool
        if event and not _id:
            new = int(event.GetId())  # get widget ID
        elif _id:
            new = _id

        self.tools[self.toggled].SetValue(True)
        if new != self.toggled:  # toggle old button
            self.tools[self.toggled].SetValue(False)
            self.tools[new].SetValue(True)

        self.toggled = new
        pub.sendMessage('canvas.change_tool', new=new)


00255     def on_transparency(self, event):
        """Toggles transparency in the shapes' background"""
        if event.Checked() and not self.gui.canvas.selected:
            self.gui.util.transparent = True
        else:
            self.gui.util.transparent = False

        if self.gui.canvas.selected:
            self.gui.canvas.toggle_transparent()
        pub.sendMessage('canvas.change_tool')


00267     def on_swap(self, event):
        """Swaps foreground/background colours"""
        a, b = self.get_background_colour(), self.get_colour()
        self.background.SetColour(b)
        self.colour.SetColour(a)

        self.gui.util.colour = a
        if not self.transparent.IsChecked():
            self.gui.util.background = a
        pub.sendMessage('canvas.change_tool')


00279     def change_colour(self, event=None, colour=None):
        """Event can also be a string representing a colour (from the grid)"""
        if event and not colour:
            colour = event.GetValue()  # from the colour button
        self.colour.SetColour(colour)
        self.update(colour, u"colour")


00287     def change_background(self, event=None, colour=None):
        """Event can also be a string representing a colour (from the grid)"""
        if event and not colour:
            colour = event.GetValue()  # from the colour button
        self.background.SetColour(colour)
        self.update(colour, u"background")


00295     def update(self, value, var_name, add_undo=True):
        """Updates the given utility variable and the selected shape"""
        setattr(self.gui.util, var_name, value)

        if self.gui.canvas.selected:
            if add_undo:
                self.gui.canvas.add_undo()
            if var_name == u"background" and not self.transparent.IsChecked():
                self.gui.canvas.selected.background = value
            elif var_name != u"background":
                setattr(self.gui.canvas.selected, var_name, value)
            self.gui.canvas.redraw_all(True)
            pub.sendMessage('update_shape_viewer')

        pub.sendMessage('canvas.change_tool')
        self.preview.Refresh()


00313     def change_thickness(self, event=None):
        """
        This uses a timer to know when a series of scroll events begin so we
        don't add many undo points
        """
        if not self.thickness_scrolling:
            self.thickness_scrolling = True
            self.thickness_timer = wx.CallLater(250, self.reset_hotkey)
            self.update(self.thickness.GetSelection(), u"thickness")
        else:
            self.thickness_timer.Restart(250)
            self.update(self.thickness.GetSelection(), u"thickness", False)


    def reset_hotkey(self):
        self.thickness_scrolling = False

#----------------------------------------------------------------------


00333 class DrawingPreview(wx.Window):
    """
    Shows a sample of what the current tool's drawing will look like.
    Pane is the collapsible pane, its parent.
    """
00338     def __init__(self, pane, gui):
        """
        Stores gui reference to access utility colour/thickness attributes.
        """
        wx.Window.__init__(self, pane)
        self.gui = gui
        self.SetBackgroundColour(wx.WHITE)
        self.SetSize((45, 45))
        self.SetToolTipString(_("A preview of your current tool"))

        self.Bind(wx.EVT_PAINT, self.on_paint)
        pub.subscribe(self.redraw, 'gui.preview.refresh')


00352     def on_paint(self, event=None):
        """
        Draws the tool inside the box when tool/colour/thickness
        is changed
        """
        if self.gui.canvas:
            dc = wx.PaintDC(self)
            dc.SetPen(wx.Pen(self.gui.canvas.shape.colour, self.gui.canvas.shape.thickness, wx.SOLID))
            if self.gui.util.transparent:
                dc.SetBrush(wx.TRANSPARENT_BRUSH)
            else:
                dc.SetBrush(self.gui.canvas.shape.brush)

            width, height = self.GetClientSize()
            self.gui.canvas.shape.preview(dc, width, height)

            dc.SetPen(wx.Pen((0, 0, 0), 1, wx.SOLID))
            dc.SetBrush(wx.TRANSPARENT_BRUSH)
            width, height = self.GetClientSize()
            dc.DrawRectangle(0, 0, width, height)  # draw a border..

    def redraw(self):
        self.Refresh()

#----------------------------------------------------------------------

00378 class MediaPanel(wx.Panel):
    """
    A panel that contains a MediaCtrl for playing videos/audio, and buttons for
    controlling it: open (file)/pause/stop/play, and a slider bar.
    Used by the Media tool.
    """
    def __init__(self, parent, pos, tool):
        wx.Panel.__init__(self, parent, pos=pos, style=wx.CLIP_CHILDREN)

        self.gui = parent.gui
        self.tool = tool
        self.offset = (0, 0)
        self.directory = None
        self.mc = wx.media.MediaCtrl(self, style=wx.SIMPLE_BORDER)
        self.timer = wx.Timer(self)  # updates the slider as the file plays
        self.SetCursor(wx.StockCursor(wx.CURSOR_ARROW))
        self.total = ""  # total time

        self.file_drop = MediaDropTarget(self)
        self.SetDropTarget(self.file_drop)

        self.open = wx.BitmapButton(self, bitmap=wx.ArtProvider.GetBitmap(wx.ART_FILE_OPEN, wx.ART_TOOLBAR))
        self.play = wx.BitmapButton(self, bitmap=wx.Bitmap(get_image_path(u"icons", u"play")))
        self.pause = wx.BitmapButton(self, bitmap=wx.Bitmap(get_image_path(u"icons", u"pause")))
        self.stop = wx.BitmapButton(self, bitmap=wx.Bitmap(get_image_path(u"icons", u"stop")))
        self.play.Disable()
        self.pause.Disable()
        self.stop.Disable()

        self.file = wx.StaticText(self)
        self.elapsed = wx.StaticText(self)
        timesizer = wx.BoxSizer(wx.HORIZONTAL)
        timesizer.Add(self.file, 1, wx.LEFT | wx.RIGHT, 5)
        timesizer.Add(self.elapsed, 0, wx.ALIGN_RIGHT | wx.RIGHT, 5)

        self.slider = wx.Slider(self)
        self.slider.SetToolTipString(_("Skip to a position"))
        self.volume = wx.Slider(self, value=100, style=wx.SL_VERTICAL | wx.SL_INVERSE)
        self.volume.SetToolTipString(_("Set the volume"))
        self.slider.SetMinSize((150, -1))
        self.volume.SetMinSize((-1, 75))

        sizer = wx.GridBagSizer(6, 5)
        sizer.Add(self.mc, (1, 1), span=(5, 1))
        sizer.Add(self.open, (1, 3), flag=wx.RIGHT, border=10)
        sizer.Add(self.play, (2, 3), flag=wx.RIGHT, border=10)
        sizer.Add(self.pause, (3, 3), flag=wx.RIGHT, border=10)
        sizer.Add(self.stop, (4, 3), flag=wx.RIGHT, border=10)
        sizer.Add(self.volume, (5, 3), flag=wx.RIGHT, border=10)
        sizer.Add(self.slider, (6, 1), flag=wx.EXPAND)
        sizer.Add(timesizer, (7, 1), flag=wx.EXPAND | wx.BOTTOM, border=10)
        self.SetSizer(sizer)
        self.Layout()
        self.Fit()

        self.Bind(wx.media.EVT_MEDIA_LOADED, self.media_loaded)
        self.Bind(wx.media.EVT_MEDIA_STOP, self.media_stopped)
        self.Bind(wx.EVT_BUTTON, self.load_file, self.open)
        self.Bind(wx.EVT_BUTTON, self.on_play, self.play)
        self.Bind(wx.EVT_BUTTON, self.on_pause, self.pause)
        self.Bind(wx.EVT_BUTTON, self.on_stop, self.stop)
        self.Bind(wx.EVT_SLIDER, self.on_seek, self.slider)
        self.Bind(wx.EVT_SLIDER, self.on_volume, self.volume)
        self.Bind(wx.EVT_LEFT_UP, self.left_up)
        self.Bind(wx.EVT_LEFT_DOWN, self.left_down)
        self.Bind(wx.EVT_MOTION, self.left_motion)
        self.Bind(wx.EVT_TIMER, self.on_timer)
        self.timer.Start(650)


00448     def left_down(self, event):
        """Grab the mouse offset of the window relative the the top-left"""
        self.gui.canvas.selected = self.tool
        self.tool.selected = True
        self.CaptureMouse()
        pos = self.Parent.ScreenToClient(self.ClientToScreen(event.Position))

        self.offset = (pos[0] - self.tool.x, pos[1] - self.tool.y)


    def left_up(self, event):
        if self.HasCapture():
            self.ReleaseMouse()
        self.Layout()


00464     def left_motion(self, event):
        """Reposition the window with an offset"""
        if event.Dragging():
            pos = self.Parent.ScreenToClient(self.ClientToScreen(event.Position))
            pos = (pos[0] - self.offset[0], pos[1] - self.offset[1])

            self.tool.x, self.tool.y = pos
            self.SetPosition(pos)


00474     def load_file(self, evt):
        """
        Display a file chooser window and try to load the file
        """
        vids = u"*.avi; *.mkv; *.mov; *.mpg; *ogg; *.wmv"
        audio = u"*.mp3; *.oga; *.ogg; *.wav"
        wildcard = _("Media Files") + u" |%s;%s|" % (vids, audio)
        wildcard += _("Video Files") + u" (%s)|%s|" % (vids, vids)
        wildcard += _("Audio Files") + u" (%s)|%s" % (audio, audio)

        _dir = u""
        if self.directory:
            _dir = self.directory

        name = file_dialog(self, _("Choose a media file"), wx.OPEN, wildcard, _dir)
        if name:
            self.do_load_file(name)


00493     def do_load_file(self, path):
        """
        Loads a file from a given path, sets up instance variables and enables
        and disabled buttons
        """
        if not self.mc.Load(path):
            wx.MessageBox(_("Unable to load %s: Unsupported format?") % path,
                         u"Whyteboard", wx.ICON_ERROR | wx.OK)
            self.play.Disable()
            self.pause.Disable()
            self.stop.Disable()
        else:
            if os.name == "posix":
                self.mc.Load(path)
        self.directory = path
        self.tool.filename = path


00511     def media_loaded(self, evt):
        """
        Called when a media file has finished loading. Calculates the total time
        of the file and updates the filename label
        """
        self.play.Enable()
        self.total = get_time(self.mc.Length() / 1000)
        wordwrap(os.path.basename(self.tool.filename), 350, wx.ClientDC(self.gui))
        self.file.SetLabel(os.path.basename(self.tool.filename))
        self.elapsed.SetLabel(u"00:00/" + self.total)
        self.mc.SetInitialSize()
        self.slider.SetRange(0, self.mc.Length())
        self.GetSizer().Layout()
        self.Fit()


    def media_stopped(self, evt):
        self.on_stop(ignore=True)

00530     def on_timer(self, evt):
        """Keep updating the timer label/scrollbar..."""
        if self.mc.GetState() == wx.media.MEDIASTATE_PLAYING:
            offset = self.mc.Tell()
            self.slider.SetValue(offset)
            self.elapsed.SetLabel(get_time(offset / 1000) + "/" + self.total)


    def on_play(self, evt):
        if not self.mc.Play():
            wx.MessageBox(_("Unable to play file %s") % self.tool.filename,
                          u"Whyteboard", wx.ICON_ERROR | wx.OK)
        else:
            self.play.Disable()
            self.pause.Enable()
            self.stop.Enable()


    def on_pause(self, evt):
        self.mc.Pause()
        self.play.Enable()
        self.pause.Disable()

    def on_stop(self, evt=None, ignore=False):
        if not ignore:
            self.mc.Stop()
        self.slider.SetValue(0)
        self.elapsed.SetLabel(u"00:00/" + self.total)
        self.play.Enable()
        self.pause.Disable()
        self.stop.Disable()

    def on_seek(self, evt):
        self.mc.Seek(self.slider.GetValue())
        self.elapsed.SetLabel(u"%s/%s" % (get_time(self.slider.GetValue() / 1000), self.total))

    def on_volume(self, evt):
        self.mc.SetVolume(float(self.volume.GetValue() / 100))


#---------------------------------------------------------------------

00572 class MediaDropTarget(wx.FileDropTarget):
    """Implements drop target functionality to receive files"""
    def __init__(self, panel):
        wx.FileDropTarget.__init__(self)
        self.panel = panel

    def OnDropFiles(self, x, y, filenames):
        self.panel.do_load_file(filenames[0])

#---------------------------------------------------------------------


00584 class SidePanel(wx.Panel):
    """
    The side panel is a tabbed window, allowing the user to switch between
    thumbnails and notes. It can be toggled on and off via CollapsiblePane
    """
    def __init__(self, gui):
        wx.Panel.__init__(self, gui, style=wx.RAISED_BORDER)
        cp = wx.CollapsiblePane(self, style=wx.CP_DEFAULT_STYLE |
                                     wx.CP_NO_TLW_RESIZE)

        self.tabs = wx.Notebook(cp.GetPane())
        self.thumbs = Thumbs(self.tabs, gui)
        self.notes = Notes(self.tabs, gui)
        self.tabs.AddPage(self.thumbs, _("Thumbnails"))
        self.tabs.AddPage(self.notes, _("Notes"))

        sizer = wx.BoxSizer(wx.VERTICAL)
        csizer = wx.BoxSizer(wx.VERTICAL)
        csizer.Add(self.tabs, 1, wx.EXPAND)
        sizer.Add(cp, 1, wx.EXPAND)

        self.SetSizer(sizer)
        cp.GetPane().SetSizer(csizer)
        cp.Expand()
        self.Bind(wx.EVT_COLLAPSIBLEPANE_CHANGED, self.toggle)


00611     def toggle(self, evt):
        """Toggles the pane and its widgets"""
        self.GetTopLevelParent().Layout()


#----------------------------------------------------------------------


00619 class Notes(wx.Panel):
    """
    Contains a Tree which shows an overview of all sheets' notes.
    Each sheet is a child of the tree, with each Note a child of a sheet.
    Sheets can be right clicked to pop-up a menu; or double clicked to change
    to that sheet.  Notes can be double/right clicked upon to be edited.
    """
    def __init__(self, parent, gui):
        wx.Panel.__init__(self, parent)
        self.gui = gui
        self.tree = wx.TreeCtrl(self, style=wx.TR_HAS_BUTTONS)
        self.root = self.tree.AddRoot("Whyteboard")
        self.tabs = []
        self.add_tab()
        self.tree.Expand(self.root)

        self.sizer = wx.BoxSizer(wx.VERTICAL)
        self.sizer.Add(self.tree, 1, wx.EXPAND)  # fills vert space
        self.SetSizer(self.sizer)
        self.tree.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.on_click)
        self.tree.Bind(wx.EVT_TREE_ITEM_RIGHT_CLICK, self.pop_up)
        pub.subscribe(self.add_note, 'note.add')
        pub.subscribe(self.edit_note, 'note.edit')
        pub.subscribe(self.rename, 'sheet.rename')
        pub.subscribe(self.sheet_moved, 'sheet.move')
        pub.subscribe(self.remove_current_sheet_items, 'note.delete_sheet_items')

00646     def add_tab(self, name=None):
        """Adds a new tab as a child to the root element"""
        _id = len(self.tabs)
        if not _id:
            _id = 0
        if not name:
            name = _("Sheet") + u" %s" % (_id + 1)

        data = wx.TreeItemData(_id)
        t = self.tree.AppendItem(self.root, name, data=data)
        self.tabs.insert(_id, t)


00659     def add_note(self, note, _id=None):
        """
        Adds a note to the current tab tree element. The notes' text is the
        element's text in the tree - newlines are replaced to stop the tree's
        formatting becoming too wide.
        """
        text = note.text.replace(u"\n", u" ")[:15]
        _id = self.tabs[self.gui.tabs.GetSelection()]

        data = wx.TreeItemData(note)
        note.tree_id = self.tree.AppendItem(_id, text, data=data)
        self.tree.Expand(_id)


00673     def remove_tab(self, note):
        """Removes a tab and its children."""
        item = self.tabs[note]
        self.tree.DeleteChildren(item)
        self.tree.Delete(item)

        del self.tabs[note]
        # now ensure all nodes are linked to the right tab
        for x in range(self.gui.current_tab, len(self.tabs)):
            self.tree.SetItemData(self.tabs[x], wx.TreeItemData(x))


00685     def rename(self, _id, text):
        """Renames a given sheet"""
        self.tree.SetItemText(self.tabs[_id], text)


00690     def remove_all(self):
        """Removes all tabs."""
        self.tree.DeleteChildren(self.root)
        self.tabs = []

    def remove_current_sheet_items(self):
        self.tree.DeleteChildren(self.tabs[self.gui.tabs.GetSelection()])

00698     def on_click(self, event):
        """
        Changes to the selected tab if a tab node is double clicked upon,
        otherwise we're editing the note.
        """
        item = self.tree.GetPyData(event.GetItem())

        if item is None:  # clicked on the root node
            return
        if isinstance(item, int):
            self.gui.tabs.SetSelection(item)
            self.gui.on_change_tab()
        else:
            item.edit()


00714     def pop_up(self, event):
        """Brings up the context menu on right click (except on root node)"""
        if self.tree.GetPyData(event.GetItem()) is not None:
            self.PopupMenu(NotesPopup(self, self.gui, event))


00720     def select(self, event, draw=True):
        """
        Selects a Note if unselected, otherwise it de-selects the note.
        draw forces a canvas redraw
        """
        item = self.tree.GetPyData(event.GetItem())

        if not item.selected:
            self.gui.canvas.deselect_shape()
            item.selected = True
            self.gui.canvas.selected = item
        else:
            self.gui.canvas.deselect_shape()

        if draw:
            self.gui.canvas.redraw_all()


    def delete(self, event):
        self.select(event, False)
        self.gui.canvas.delete_selected()

00742     def edit_note(self, tree_id, text):
        """Edit a non-blank Note by changing its tree item's text"""
        text = text.replace(u"\n", u" ")[:15]
        self.tree.SetItemText(tree_id, text)


00748     def sheet_moved(self, event, tab_count):
        """
        Drag/drop sheet: move a tree item and its associated notes
        """
        tree = self.tree

        old_item = self.tabs[event.GetOldSelection()]
        text = tree.GetItemText(old_item)
        children = []

        # Save all Note item data to re-create it in the new Tree node
        if tree.ItemHasChildren(old_item):
            (child, cookie) = tree.GetFirstChild(old_item)
            while child.IsOk():
                item = (tree.GetItemPyData(child), tree.GetItemText(child))
                children.append(item)
                (child, cookie) = tree.GetNextChild(old_item, cookie)

        # Remove the old tree node, re-add it
        before = event.GetSelection()

        if event.GetSelection() >= tab_count:
            before = event.GetSelection() - 1
        if event.GetOldSelection() < event.GetSelection():  # drag to the right
            before += 1
        if before < 0:
            before = 0

        new = tree.InsertItemBefore(self.root, before, text)
        tree.Delete(old_item)

        # Restore the notes to the new tree item
        for item in children:
            data = wx.TreeItemData(item[0])
            item[0].tree_id = tree.AppendItem(new, item[1], data=data)

        # Reposition the tab in the list of wx.TreeItemID's for the loop below
        item = self.tabs.pop(event.GetOldSelection())
        self.tabs.insert(event.GetSelection(), item)

        # Update each tree's node data so it is pointing to the correct tab ID
        (child, cookie) = tree.GetFirstChild(self.root)
        count = 0

        while child.IsOk():
            self.tabs[count] = child
            tree.SetItemData(self.tabs[count], wx.TreeItemData(count))
            (child, cookie) = tree.GetNextChild(self.root, cookie)
            count += 1

        tree.Expand(new)


#----------------------------------------------------------------------


00804 class Thumbs(scrolled.ScrolledPanel):
    """
    Thumbnails of all tabs' drawings.
    """
    def __init__(self, parent, gui):
        scrolled.ScrolledPanel.__init__(self, parent, style=wx.VSCROLL)
        self.sizer = wx.BoxSizer(wx.VERTICAL)
        self.SetSizer(self.sizer)
        self.SetScrollRate(0, 120)

        self.gui = gui
        self.thumbs = []  # ThumbButtons
        self.text = []  # StaticTexts
        self.new_thumb()  # inital thumb
        self.thumbs[0].current = True
        pub.subscribe(self.highlight_current, 'thumbs.text.highlight')
        pub.subscribe(self.rename, 'sheet.rename')
        pub.subscribe(self.sheet_moved, 'sheet.move')
        pub.subscribe(self.update_current, 'thumbs.update_current')


    def highlight_current(self, tab, select):
        if self.text:
            try:
                font = self.text[tab].GetClassDefaultAttributes().font
                font.SetWeight(wx.FONTWEIGHT_NORMAL)
                if select:
                    font.SetWeight(wx.FONTWEIGHT_BOLD)

                self.text[tab].SetFont(font)
            except IndexError:
                pass  # ignore a bug closing the last tab from the pop-up menu
                      # temp fix, can't think how to solve it otherwise


00839     def new_thumb(self, _id=0, name=None):
        """
        Creates a new thumbnail button and manages its ID, along with a label.
        """
        if _id:
            bmp = self.redraw(_id)
        else:
            if len(self.thumbs):
                _id = len(self.thumbs)

            bmp = wx.EmptyBitmap(150, 150)
            memory = wx.MemoryDC()
            memory.SelectObject(bmp)
            memory.SetPen(wx.Pen((255, 255, 255), 1))
            memory.SetBrush(wx.Brush((255, 255, 255)))
            memory.Clear()
            memory.FloodFill(0, 0, (255, 255, 255), wx.FLOOD_BORDER)
            if os.name == "nt":
                memory.SetPen(wx.Pen(wx.BLACK, 1, wx.SOLID))
                memory.SetBrush(wx.TRANSPARENT_BRUSH)
                memory.DrawRectangle(0, 0, 150, 150)
            memory.SelectObject(wx.NullBitmap)

        btn = ThumbButton(self, _id, bmp, name)
        if not name:
            name = _("Sheet") + u" %s" % (_id + 1)

        text = wx.StaticText(self, label=name)
        self.text.insert(_id, text)
        self.thumbs.insert(_id, btn)

        self.sizer.Add(text, 0, wx.ALIGN_CENTER | wx.TOP, 13)
        self.sizer.Add(btn, 0, wx.ALIGN_CENTER | wx.TOP, 7)
        self.SetVirtualSize(self.GetBestVirtualSize())


00875     def remove(self, _id):
        """
        Removes a thumbnail/label from the sizer and the managed widgets list.
        """
        self.sizer.Remove(self.thumbs[_id])
        self.sizer.Remove(self.text[_id])
        self.thumbs[_id].Hide()  # 'visibly' remove
        self.text[_id].Hide()

        del self.thumbs[_id]  # 'physically' remove
        del self.text[_id]
        self.SetVirtualSize(self.GetBestVirtualSize())

        # now ensure all thumbnail classes are pointing to the right tab
        for x in range(self.gui.current_tab, len(self.thumbs)):
            self.thumbs[x].thumb_id = x


00893     def remove_all(self):
        """
        Removes all thumbnails.
        """
        for x in range(len(self.thumbs)):
            self.sizer.Remove(self.thumbs[x])
            self.sizer.Remove(self.text[x])
            self.thumbs[x].Hide()  # 'visibly' remove
            self.text[x].Hide()
            self.sizer.Layout()  # update sizer

        self.thumbs = []
        self.text = []
        self.SetVirtualSize(self.GetBestVirtualSize())


00909     def redraw(self, _id):
        """
        Create a thumbnail by grabbing the currently selected Whyteboard's
        contents and creating a bitmap from it. This bitmap is then converted
        to an image to rescale it, and converted back to a bitmap to be
        displayed on the button as the thumbnail.
        """
        canvas = self.gui.tabs.GetPage(_id)
        img = wx.ImageFromBitmap(canvas.buffer)
        img.Rescale(150, 150)
        bitmap = wx.BitmapFromImage(img)

        if os.name == "nt":
            memory = wx.MemoryDC()
            memory.SelectObject(bitmap)
            memory.SetPen(wx.Pen(wx.BLACK, 1, wx.SOLID))
            memory.SetBrush(wx.TRANSPARENT_BRUSH)
            memory.DrawRectangle(0, 0, 150, 150)
            memory.SelectObject(wx.NullBitmap)
        return bitmap


    def sheet_moved(self, event, tab_count):
        for x in range(tab_count):
            self.text[x].SetLabel(self.gui.tabs.GetPageText(x))


    def rename(self, _id, text):
        self.text[_id].SetLabel(text)
        self.Layout()

00940     def update_current(self):
        """
        Updates current thumnbail
        """
        self.update(self.gui.tabs.GetSelection())

00946     def update(self, _id):
        """
        Updates a single thumbnail.
        """
        bmp = self.redraw(_id)
        thumb = self.thumbs[_id]
        thumb.SetBitmapLabel(bmp)
        self.thumbs[_id].buffer = bmp

        if thumb.current and meta.transparent:
            thumb.highlight()


00959     def update_all(self):
        """
        Updates all thumbnails (i.e. upon loading a Whyteboard file).
        """
        for count, item in enumerate(self.thumbs):
            self.update(count)


#----------------------------------------------------------------------


00970 class ThumbButton(wx.BitmapButton):
    """
    This class has an extra attribute, storing its related tab ID so that when
    the button is pressed, it can switch to the proper tab.
    """
    def __init__(self, parent, _id, bitmap, name=None):
        wx.BitmapButton.__init__(self, parent, size=(150, 150))
        self.thumb_id = _id
        self.parent = parent
        self.SetBitmapLabel(bitmap)
        self.buffer = bitmap
        self.current = False  # active thumb?
        self.Bind(wx.EVT_BUTTON, self.on_press)
        self.SetBackgroundColour(wx.WHITE)
        self.Bind(wx.EVT_RIGHT_UP, self.tab_popup)

00986     def tab_popup(self, event):
        """ Pops up the tab context menu. """
        self.PopupMenu(ThumbsPopup(self.parent, self.parent.gui, self.thumb_id))


00991     def on_press(self, event):
        """
        Changes the tab to the selected button, deselect previous one
        """
        self.parent.gui.tabs.SetSelection(self.thumb_id)
        self.parent.gui.on_change_tab()


00999     def update(self):
        """
        Iterates over each thumb and unhighlights the previously selected thumb
        Then, sets this thumb as the currently highlighted one and redraws
        """
        for thumb in self.parent.thumbs:
            if thumb.thumb_id != self.thumb_id:
                if thumb.current:
                    thumb.current = False
                    self.parent.update(thumb.thumb_id)

        self.current = True
        self.parent.update(self.thumb_id)


01014     def highlight(self):
        """
        Highlights the current thumbnail with a light transparent overlay.
        """
        dc = wx.MemoryDC()
        dc.SelectObject(self.buffer)

        gcdc = wx.GCDC(dc)
        gcdc.SetBrush(wx.Brush(wx.Color(0, 0, 255, 50)))  # light blue
        gcdc.SetPen(wx.Pen((0, 0, 0), 1, wx.TRANSPARENT))
        gcdc.DrawRectangle(0, 0, 150, 150)

        dc.SelectObject(wx.NullBitmap)
        self.SetBitmapLabel(self.buffer)

Generated by  Doxygen 1.6.0   Back to index