summaryrefslogtreecommitdiff
path: root/cad/src/experimental/demoapp_0.1/demoapp/ui/DemoAppWindow.py
blob: 6697c20824f33dbab6f0a2124354ee054a8d1a79 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
"""
DemoAppWindow.py

$Id$
"""

import pyglet

from pyglet.gl import *

from pyglet import font

from demoapp.tools.TrivalentGraphDrawingTool import TrivalentGraphDrawingTool

from demoapp.tools.DeleteNodeTool import DeleteNodeTool

from demoapp.models.TrivalentGraphModel import TrivalentGraphModel

from demoapp.widgets.controls import TextButton
from demoapp.widgets.ChildHolder import EventDistributorToChildren

from pyglet.window import key

from demoapp.geometry.vectors import A, V

# ==

class AppWindow(pyglet.window.Window): ## REFILE
    # tooltip text object [modified from soundspace.py]
    # todo: on main window only, not every pane
    # todo: put text into a nice rectangle with translucent bg color
    # todo: user pref to position it, rel to object or in std corner of screen or in user-movable window
    _tip_text_object = font.Text(font.load('', 10),
                                      '',
                                      color = (0, 0, 0, 1),
                                      halign = 'center',
                                      valign = 'top' )
        # note: self._tip_text_object.text gets reset by other code as needed;
        # tip .x .y get reset as part of drawing it
    def draw_tip_and_highlight(self, stuff, instance):
        try:
            if type(stuff) == type([]):
                for thing in stuff:
                    self.draw_tip_and_highlight(thing, instance)
            elif type(stuff) == type(()):
                # a drawing cmd desc...
                ## name, args = stuff[0], stuff[1:]
                name, args = stuff ## nonstandard structure, args already a tuple... change? put in a class? #####DECIDE
                method = getattr(instance, name)
                ## print "draw_tip_and_highlight got method %r for args %r len %d" % (method, args, len(args))
                method(*args)
                    # this can indirectly call e.g. self.draw_tooltip_text
            return
        except:
            print "following exception is in x.draw_tip_and_highlight(%r, %r):" % \
                  (stuff, instance)
            raise
        pass
    def draw_tooltip_text( self, text, pos, size):
        #doc
        # todo: use size
        text_object = self._tip_text_object
        if text_object.text != text:
            text_object.text = text
        text_object.x, text_object.y = pos + V(10, -20) # assume LL corner of text
            # GUESS and stub;
            # maybe be smarter, let caller pass offset directions to avoid;
            # future: transform from model to pane coords
        text_object.draw()
    pass


class ToolPane(pyglet.event.EventDispatcher):
    x = y = 0 # KLUGE, hides some bugs -- opengl and tooltip drawing need transforms #####
    model = None
    event_types = list(pyglet.window.Window.event_types) # kluge?
    _last_tool = None
    def __init__(self, parent):
        self.parent = parent
    def find_object(self, x, y, excluding = ()):
        for obj in self.model.hit_test_objects():
            if obj.hit_test(x, y) and not obj in excluding:
                return obj
        return None
    def draw_tip_and_highlight(self, stuff, instance):
        return self.parent.draw_tip_and_highlight(stuff, instance)
    def draw_tooltip_text(self, text, pos, size):
        return self.parent.draw_tooltip_text(text, pos, size)
    def set_tool(self, tool):
        if self._last_tool:
            self._last_tool.deactivate()
        self._last_tool = tool
        tool.activate()
    pass

ToolPane.register_event_type('on_draw_handle')


class DemoAppWindow(AppWindow):
    GUI_WIDTH = 400
    GUI_HEIGHT = 40
    GUI_PADDING = 4
    GUI_BUTTON_HEIGHT = 16
    def __init__(self, *args, **kws):
        super(DemoAppWindow, self).__init__(*args, **kws)
        toolpane = ToolPane(self)
        toolpane.model = TrivalentGraphModel()
        self.tool1 = TrivalentGraphDrawingTool(toolpane)
        self.tool2 = DeleteNodeTool(toolpane)

        self._tool_button_table = [
            # todo: refactor to external name->class table
            # todo: make button width adapt to text changes -- for now,
            #  the hardcoded width requires the text to be this short
            ("Tool1", self.tool1),
            ("Tool2", self.tool2),
         ]

        self._current_tool_index = 0

        self.toolpane = toolpane

        self.children_besides_controls = [toolpane] # not counting self.controls... (kluge)
        self._event_distributor = EventDistributorToChildren(self.find_child) # finds both kinds of children
        self.push_handlers( self._event_distributor)

        # view transform [modified from soundspace.py] ###@@@ USE ME
        self.zoom = 40 # pixels per unit
        self.tx = self.width/2 #k review: 2.0?
        self.ty = self.height/2

        # from controls.py test code, aka media_player.py pyglet example code
        self.change_tool_button = TextButton(self)
        self.change_tool_button.x = self.GUI_PADDING
        self.change_tool_button.y = self.GUI_PADDING
        self.change_tool_button.height = self.GUI_BUTTON_HEIGHT
        self.change_tool_button.width = 45
        self.change_tool_button.on_press = self.change_to_next_tool

        win = self
        self.window_button = TextButton(self)
        self.window_button.x = self.change_tool_button.x + \
                               self.change_tool_button.width + self.GUI_PADDING
        self.window_button.y = self.GUI_PADDING
        self.window_button.height = self.GUI_BUTTON_HEIGHT
        self.window_button.width = 90
        self.window_button.text = 'Windowed'
        self.window_button.on_press = lambda: win.set_fullscreen(False)

        self.controls = [
            # self.slider,
            self.change_tool_button, # demo
            self.window_button, # useful
        ]

        x = self.window_button.x + self.window_button.width + self.GUI_PADDING
        i = 0
        for screen in self.display.get_screens():
            screen_button = TextButton(self)
            screen_button.x = x
            screen_button.y = self.GUI_PADDING
            screen_button.height = self.GUI_BUTTON_HEIGHT
            screen_button.width = 80
            screen_button.text = 'Screen %d' % (i + 1)
            screen_button.on_press = \
                (lambda s: lambda: win.set_fullscreen(True, screen=s))(screen)
            self.controls.append(screen_button)
            i += 1
            x += screen_button.width + self.GUI_PADDING
            break ### KLUGE -- avoid bug, 2nd screen button on my silver mac gets this when tried:
                # CarbonException: invalid drawable

        self.gui_update_state()

    def find_child(self, x, y):
        if 1:
            # this was causing a bug with drags:
            # - the controls already got these events from their capture_events pushed handler [fix: it's disabled]
            #   - which failed to return EVENT_HANDLED [fixed]
            # - then this makes EventDistributor give them the events [still true], but with transformed coords [fixed]
            #   which they don't want (maybe that should be an option for children, F for them).
            # BUT this is required for them to feel the press in front of the tool. [080616 714p; fixed 930p]
            for control in self.controls:
                if control.hit_test(x, y):
                    return control
        #e could test y, no real need; should generalize the following
        assert len(self.children_besides_controls) == 1
        return self.children_besides_controls[0]

    def gui_update_state(self):
        current_tool_data = self._tool_button_table[ self._current_tool_index]
        label, tool = current_tool_data
        self.change_tool_button.text = label
        self.toolpane.set_tool( tool)

    def on_key_press(self, symbol, modifiers):
        if symbol == key.SPACE:
            self.change_to_next_tool()
        elif symbol == key.ESCAPE:
            self.dispatch_event('on_close')

    def on_close(self):
        self.close()

    def change_to_next_tool(self):
        """
        change to the next tool
        """
        self._current_tool_index += 1
        self._current_tool_index %= len( self._tool_button_table)
        self.gui_update_state()

    def on_draw(self):
        glClearColor(0.95, 0.95, 0.95, 1)
        ## glClearColor(1, 1, 1, 1) # see if this fixes button text color -- nope.

        self.clear()
        ## self.model.draw()
        for child in self.children_besides_controls:
            child.model.draw() ### BUG: WRONG COORDS ### REFACTOR, child.draw do this
            child.dispatch_event('on_draw_handle') # my own event, to draw the handle rubberbands etc for the ones that need that
        ## call the drawing functions of the handlers on the stack?? have a "draw overlay event" they can pass down?
        # issue: they probably want to call the behind-stuff recursively...

        for control in self.controls:
            control.draw()
        return

    pass

# end