1# Copyright (c) 2009, Tomohiro Kusumi
2# All rights reserved.
3#
4# Redistribution and use in source and binary forms, with or without
5# modification, are permitted provided that the following conditions are met:
6#
7# 1. Redistributions of source code must retain the above copyright notice, this
8#    list of conditions and the following disclaimer.
9# 2. Redistributions in binary form must reproduce the above copyright notice,
10#    this list of conditions and the following disclaimer in the documentation
11#    and/or other materials provided with the distribution.
12#
13# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
14# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
17# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
20# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23
24from . import console
25from . import edit
26from . import extension
27from . import panel
28from . import screen
29from . import setting
30from . import status
31from . import util
32from . import virtual
33from . import visual
34from . import void
35from . import window
36
37BUILD_FAILED = -1
38BUILD_RETRY  = -2
39
40class Workspace (object):
41    def __init__(self, bpl):
42        self.__bpl = 1
43        self.set_bytes_per_line(bpl)
44        assert self.__bpl >= 1, self.__bpl
45        self.__windows = ()
46        self.__fileopss = []
47        self.__consoles = []
48        self.__vwindows = {}
49        self.__bwindows = {}
50        self.__twindows = {}
51        self.__swindows = {}
52        self.__cur_fileops = None
53        self.__cur_console = None
54        self.__set_window(None)
55        self.__def_vwindow, \
56        self.__def_bwindow, \
57        self.__def_twindow, \
58        self.__def_swindow \
59        = self.__windows
60
61    def __getattr__(self, name):
62        if name == "_Workspace__cur_fileops":
63            raise AttributeError(name)
64        return getattr(self.__cur_fileops, name)
65
66    def __len__(self):
67        return len(self.__windows)
68
69    def dispatch(self):
70        return -1
71
72    def __set_buffer(self, o):
73        self.__cur_fileops = o
74        self.set_console(None, None)
75
76    def set_console(self, con, arg):
77        if isinstance(con, console.Console):
78            self.__cur_console = con
79        else:
80            i = self.__fileopss.index(self.__cur_fileops)
81            self.__cur_console = self.__consoles[i]
82        self.__set_window(self.__cur_console)
83        for o in self.__windows:
84            if o:
85                o.set_buffer(self.__cur_fileops)
86        def dispatch():
87            self.require_full_repaint()
88            return self.__cur_console.dispatch(arg)
89        self.dispatch = dispatch
90
91    def __set_window(self, con):
92        # virtual window comes first
93        if isinstance(con, console.Console):
94            cls = util.get_class(con)
95        else:
96            cls = console.get_default_class()
97        self.__windows = \
98            self.__get_virtual_window(cls), \
99            self.__get_binary_window(cls), \
100            self.__get_text_window(cls), \
101            self.__get_status_window(cls)
102
103    def disconnect_window(self):
104        # only leave virtual window to disable repaint
105        self.__windows = self.__windows[0],
106
107    def reconnect_window(self):
108        # bring back windows of current console
109        self.__set_window(self.__cur_console)
110
111    def clone(self):
112        ret = Workspace(self.__bpl)
113        for i, o in enumerate(self.__fileopss):
114            # do shallow copy, but don't use copy.copy()
115            ret.add_buffer(i, o.clone(), self.__consoles[i])
116        return ret
117
118    def add_buffer(self, i, fop, con):
119        self.__fileopss.insert(i, fop)
120        self.__consoles.insert(i, con)
121        if len(self.__fileopss) == 1:
122            self.__set_buffer(fop)
123
124    def remove_buffer(self, i):
125        o = self.__fileopss[i]
126        i = self.__fileopss.index(o)
127        if self.__cur_fileops == o:
128            if i == len(self.__fileopss) - 1:
129                self.switch_to_prev_buffer()
130            else:
131                self.switch_to_next_buffer()
132        self.__fileopss.remove(self.__fileopss[i])
133        self.__consoles.remove(self.__consoles[i])
134
135    def switch_to_buffer(self, i):
136        self.__set_buffer(self.__fileopss[i])
137
138    def switch_to_first_buffer(self):
139        self.__set_buffer(self.__fileopss[0])
140
141    def switch_to_last_buffer(self):
142        self.__set_buffer(self.__fileopss[-1])
143
144    def switch_to_next_buffer(self):
145        if len(self.__fileopss) > 1:
146            i = self.__fileopss.index(self.__cur_fileops)
147            if i >= len(self.__fileopss) - 1:
148                o = self.__fileopss[0]
149            else:
150                o = self.__fileopss[i + 1]
151            self.__set_buffer(o)
152
153    def switch_to_prev_buffer(self):
154        if len(self.__fileopss) > 1:
155            i = self.__fileopss.index(self.__cur_fileops)
156            if i <= 0:
157                o = self.__fileopss[len(self.__fileopss) - 1]
158            else:
159                o = self.__fileopss[i - 1]
160            self.__set_buffer(o)
161
162    def iter_buffer(self):
163        for o in self.__fileopss:
164            yield o
165
166    def get_build_size(self):
167        return self.get_height(), self.get_width()
168
169    def get_height(self):
170        bin_hei = self.__def_bwindow.get_size_y()
171        tex_hei = self.__def_twindow.get_size_y() if setting.use_text_window \
172            else bin_hei
173        sta_hei = self.__def_swindow.get_size_y()
174        sta_hei_ = self.__get_status_window_height()
175        # screen size may change before build() while resizing
176        if not screen.test_soft_resize():
177            assert bin_hei == tex_hei, (bin_hei, tex_hei)
178            assert sta_hei == sta_hei_, (sta_hei, sta_hei_)
179        return bin_hei + sta_hei
180
181    def get_width(self):
182        bin_wid = self.__def_bwindow.get_size_x()
183        tex_wid = self.__def_twindow.get_size_x() if setting.use_text_window \
184            else 0
185        sta_wid = self.__def_swindow.get_size_x()
186        ret = bin_wid + tex_wid
187        # screen size may change before build() while resizing
188        if not screen.test_soft_resize():
189            assert ret == sta_wid, (bin_wid, tex_wid, sta_wid)
190        return ret
191
192    def __get_status_window_class(self):
193        return status.get_status_canvas_class(), status.get_status_frame_class()
194
195    def __get_status_window_height(self):
196        return window.get_status_window_height(
197            *self.__get_status_window_class())
198
199    # horizontal split
200    def build_dryrun_delta(self, hei_delta, beg_delta):
201        hei = self.__def_bwindow.get_size_y() + self.__def_swindow.get_size_y()
202        beg = self.__def_bwindow.get_position_y()
203        return self.build_dryrun(hei + hei_delta, beg + beg_delta)
204
205    def build_dryrun(self, hei, beg):
206        sta_hei = self.__get_status_window_height()
207        min_hei = window.get_min_binary_window_height() + sta_hei
208        return self.__test_build(hei, beg, min_hei)
209
210    def build(self, hei, beg):
211        sta_hei = self.__get_status_window_height()
212        self.__build(hei, beg, sta_hei)
213        return hei
214
215    def build_fixed_size_dryrun(self, lpw, beg):
216        sta_hei = self.__get_status_window_height()
217        hei = window.get_min_binary_window_height(lpw) + sta_hei
218        min_hei = window.get_min_binary_window_height() + sta_hei
219        return self.__test_build(hei, beg, min_hei)
220
221    def build_fixed_size(self, lpw, beg):
222        sta_hei = self.__get_status_window_height()
223        hei = window.get_min_binary_window_height(lpw) + sta_hei
224        self.__build(hei, beg, sta_hei)
225        return hei
226
227    def __test_build(self, hei, beg, min_hei):
228        scr_hei = screen.get_size_y()
229        scr_wid = screen.get_size_x()
230        if hei <= 0:
231            return BUILD_FAILED
232        if beg < 0:
233            return BUILD_FAILED
234        if scr_hei < min_hei: # screen height < minimum workspace height
235            return BUILD_FAILED
236        if hei < min_hei: # height < minimum workspace height
237            return BUILD_FAILED
238        if hei > scr_hei: # height > screen height
239            return BUILD_FAILED
240        if beg + hei >= scr_hei: # this workspace exceeds screen size
241            return BUILD_FAILED
242        if self.guess_width() > scr_wid: # test width
243            return BUILD_FAILED
244        return hei
245
246    def __build(self, hei, beg, sta_hei):
247        def vfn(o):
248            siz = hei - sta_hei, self.__guess_virtual_window_width()
249            pos = beg, 0
250            self.__build_window(o, siz, pos)
251        def bfn(o):
252            siz = hei - sta_hei, self.__guess_binary_window_width()
253            pos = beg, 0
254            self.__build_window(o, siz, pos)
255        def tfn(o):
256            siz = hei - sta_hei, self.__guess_text_window_width()
257            pos = beg, self.__def_bwindow.get_size_x()
258            self.__build_window(o, siz, pos)
259        def sfn(o):
260            siz = sta_hei, self.guess_width()
261            pos = beg + self.__def_bwindow.get_size_y(), 0
262            self.__build_window(o, siz, pos)
263        self.__do_build(vfn, bfn, tfn, sfn)
264
265    # vertical split
266    def vbuild_dryrun(self, beg):
267        sta_hei = self.__get_status_window_height()
268        hei = console.get_position_y()
269        min_hei = window.get_min_binary_window_height() + sta_hei
270        return self.__test_vbuild(hei, beg, min_hei)
271
272    def vbuild(self, beg):
273        sta_hei = self.__get_status_window_height()
274        hei = console.get_position_y()
275        self.__vbuild(hei, beg, sta_hei)
276        return beg
277
278    def vbuild_fixed_size_dryrun(self, lpw, beg):
279        sta_hei = self.__get_status_window_height()
280        hei = window.get_min_binary_window_height(lpw) + sta_hei
281        min_hei = window.get_min_binary_window_height() + sta_hei
282        return self.__test_vbuild(hei, beg, min_hei)
283
284    def vbuild_fixed_size(self, lpw, beg):
285        sta_hei = self.__get_status_window_height()
286        hei = window.get_min_binary_window_height(lpw) + sta_hei
287        self.__vbuild(hei, beg, sta_hei)
288        return beg
289
290    def __test_vbuild(self, hei, beg, min_hei):
291        scr_hei = screen.get_size_y()
292        scr_wid = screen.get_size_x()
293        if hei <= 0:
294            return BUILD_FAILED
295        if beg < 0:
296            return BUILD_FAILED
297        if scr_hei < min_hei: # screen height < minimum workspace height
298            return BUILD_FAILED
299        if hei < min_hei: # height < minimum workspace height
300            return BUILD_FAILED
301        if hei > scr_hei: # height > screen height (redundant)
302            return BUILD_FAILED
303        if hei >= scr_hei: # this workspace exceeds screen size
304            return BUILD_FAILED
305        if beg + self.guess_width() > scr_wid: # test width
306            return BUILD_FAILED
307        return hei
308
309    def __vbuild(self, hei, beg, sta_hei):
310        def vfn(o):
311            siz = hei - sta_hei, self.__guess_virtual_window_width()
312            pos = 0, beg
313            self.__build_window(o, siz, pos)
314        def bfn(o):
315            siz = hei - sta_hei, self.__guess_binary_window_width()
316            pos = 0, beg
317            self.__build_window(o, siz, pos)
318        def tfn(o):
319            siz = hei - sta_hei, self.__guess_text_window_width()
320            pos = 0, beg + self.__def_bwindow.get_size_x()
321            self.__build_window(o, siz, pos)
322        def sfn(o):
323            siz = sta_hei, self.guess_width()
324            pos = self.__def_bwindow.get_size_y(), beg
325            self.__build_window(o, siz, pos)
326        self.__do_build(vfn, bfn, tfn, sfn)
327
328    # common for both horizontal and vertical split
329    def __do_build(self, vfn, bfn, tfn, sfn):
330        self.__build_virtual_window = vfn
331        self.__build_binary_window = bfn
332        self.__build_text_window = tfn
333        self.__build_status_window = sfn
334
335        # update first for potential window parameter changes
336        # (if yes, dryrun must have been done with new parameters)
337        for o in self.__vwindows.values():
338            if o:
339                o.update()
340        for o in self.__bwindows.values():
341            if o:
342                o.update()
343        for o in self.__twindows.values():
344            if o:
345                o.update()
346        for o in self.__swindows.values():
347            if o:
348                o.update()
349
350        for o in self.__vwindows.values():
351            self.__build_virtual_window(o)
352        for o in self.__bwindows.values():
353            self.__build_binary_window(o)
354        for o in self.__twindows.values():
355            self.__build_text_window(o)
356        for o in self.__swindows.values():
357            self.__build_status_window(o)
358
359        bin_hei = self.__def_bwindow.get_size_y()
360        tex_hei = self.__def_twindow.get_size_y() if setting.use_text_window \
361            else bin_hei
362        assert bin_hei == tex_hei, (bin_hei, tex_hei)
363
364        bin_wid = self.__def_bwindow.get_size_x()
365        tex_wid = self.__def_twindow.get_size_x() if setting.use_text_window \
366            else 0
367        sta_wid = self.__def_swindow.get_size_x()
368        assert bin_wid + tex_wid == sta_wid, (bin_wid, tex_wid, sta_wid)
369
370    def __build_virtual_window(self, o):
371        self.__build_window(o, None, None)
372
373    def __build_binary_window(self, o):
374        self.__build_window(o, None, None)
375
376    def __build_text_window(self, o):
377        self.__build_window(o, None, None)
378
379    def __build_status_window(self, o):
380        self.__build_window(o, None, None)
381
382    # Note that update_capacity() can't be called from window.Window.__init__,
383    # because initial panel size hasn't been set yet, while get_bufmap() needs
384    # panel size to update bufmap.
385    def __build_window(self, o, siz, pos):
386        if o: # None if window is disabled
387            o.resize(siz, pos)
388            o.update_capacity(self.__bpl) # XXX
389
390    def __guess_virtual_window_width(self):
391        return window.get_width(panel.BinaryCanvas, self.__bpl)
392
393    def __guess_binary_window_width(self):
394        return window.get_width(panel.BinaryCanvas, self.__bpl)
395
396    def __guess_text_window_width(self):
397        if setting.use_text_window:
398            return window.get_width(panel.TextCanvas, self.__bpl)
399        else:
400            return 0
401
402    def guess_width(self):
403        return self.__guess_binary_window_width() + \
404            self.__guess_text_window_width()
405
406    def __get_virtual_window(self, cls):
407        if cls not in self.__vwindows:
408            if cls is console.get_default_class():
409                o = window.Window(virtual.BinaryCanvas, virtual.NullFrame)
410            elif cls is void.ExtConsole:
411                o = window.Window(virtual.ExtCanvas, virtual.NullFrame)
412            elif cls is visual.Console:
413                o = self.__def_vwindow
414            elif cls is visual.ExtConsole:
415                o = self.__get_virtual_window(void.ExtConsole)
416            elif util.is_subclass(cls, edit.Console):
417                o = self.__def_vwindow
418            else:
419                assert False, cls
420            self.__build_virtual_window(o)
421            self.__vwindows[cls] = o
422        return self.__vwindows[cls]
423
424    def __get_binary_window(self, cls):
425        if cls not in self.__bwindows:
426            if cls is console.get_default_class():
427                o = window.Window(panel.BinaryCanvas, panel.Frame)
428            elif cls is void.ExtConsole:
429                o = window.Window(extension.ExtBinaryCanvas, panel.Frame)
430            elif cls is visual.Console:
431                o = window.Window(visual.BinaryCanvas, panel.Frame)
432            elif cls is visual.ExtConsole:
433                o = window.Window(visual.ExtBinaryCanvas, panel.Frame)
434            elif util.is_subclass(cls, edit.WriteBinaryConsole):
435                o = window.Window(edit.WriteBinaryCanvas, panel.Frame)
436            elif util.is_subclass(cls, edit.WriteAsciiConsole):
437                o = self.__def_bwindow
438            elif util.is_subclass(cls, edit.DeleteConsole):
439                o = self.__def_bwindow
440            else:
441                assert False, cls
442            self.__build_binary_window(o)
443            self.__bwindows[cls] = o
444        return self.__bwindows[cls]
445
446    def __get_text_window(self, cls):
447        if cls not in self.__twindows:
448            if not setting.use_text_window:
449                o = None
450            elif cls is console.get_default_class():
451                o = window.Window(panel.TextCanvas, panel.Frame)
452            elif cls is void.ExtConsole:
453                o = window.Window(extension.ExtTextCanvas, panel.Frame)
454            elif cls is visual.Console:
455                o = window.Window(visual.TextCanvas, panel.Frame)
456            elif cls is visual.ExtConsole:
457                o = self.__get_text_window(void.ExtConsole)
458            elif util.is_subclass(cls, edit.Console):
459                o = self.__def_twindow
460            else:
461                assert False, cls
462            self.__build_text_window(o)
463            self.__twindows[cls] = o
464        return self.__twindows[cls]
465
466    def __get_status_window(self, cls):
467        if cls not in self.__swindows:
468            if cls is console.get_default_class():
469                o = window.Window(*self.__get_status_window_class())
470            elif cls is void.ExtConsole:
471                o = self.__def_swindow
472            elif cls is visual.Console:
473                o = self.__def_swindow
474            elif cls is visual.ExtConsole:
475                o = self.__def_swindow
476            elif util.is_subclass(cls, edit.Console):
477                o = self.__def_swindow
478            else:
479                assert False, cls
480            self.__build_status_window(o)
481            self.__swindows[cls] = o
482        return self.__swindows[cls]
483
484    def init_buffer(self, b):
485        return self.__cur_fileops.init_buffer(b)
486
487    def read(self, x, n):
488        return self.__cur_fileops.read(x, n)
489
490    def read_current(self, n):
491        return self.__cur_fileops.read(self.get_pos(), n)
492
493    def insert(self, x, l, rec=True):
494        self.__cur_fileops.insert(x, l, rec)
495
496    def insert_current(self, l, rec=True):
497        self.__cur_fileops.insert(self.get_pos(), l, rec)
498
499    def replace(self, x, l, rec=True):
500        self.__cur_fileops.replace(x, l, rec)
501
502    def replace_current(self, l, rec=True):
503        self.__cur_fileops.replace(self.get_pos(), l, rec)
504
505    def delete(self, x, n, rec=True):
506        self.__cur_fileops.delete(x, n, rec)
507
508    def delete_current(self, n, rec=True):
509        self.__cur_fileops.delete(self.get_pos(), n, rec)
510
511    def set_focus(self, x):
512        for o in self.__windows:
513            if o:
514                if o in self.__bwindows.values():
515                    o.set_focus(x)
516                elif o in self.__twindows.values():
517                    o.set_focus(x)
518                else:
519                    o.lrepaint(x, False) # entire window
520
521    def require_full_repaint(self):
522        for o in self.__windows:
523            if o:
524                o.require_full_repaint()
525
526    def repaint(self, is_current):
527        for o in self.__windows:
528            if o:
529                o.repaint(is_current)
530
531    def lrepaint(self, is_current, low):
532        for o in self.__windows:
533            if o:
534                o.lrepaint(is_current, low)
535
536    def prepaint(self, is_current, low, num):
537        for o in self.__windows:
538            if o:
539                o.prepaint(is_current, low, num)
540
541    def xrepaint(self, is_current):
542        for o in self.__windows:
543            if o:
544                o.xrepaint(is_current)
545
546    def go_up(self, n=1):
547        for o in self.__windows:
548            if o:
549                if o.go_up(n) == -1:
550                    return -1
551
552    def go_down(self, n=1):
553        for o in self.__windows:
554            if o:
555                if o.go_down(n) == -1:
556                    return -1
557
558    def go_left(self, n=1):
559        for o in self.__windows:
560            if o:
561                if o.go_left(n) == -1:
562                    return -1
563
564    def go_right(self, n=1):
565        for o in self.__windows:
566            if o:
567                if o.go_right(n) == -1:
568                    return -1
569
570    def go_pprev(self, n):
571        for o in self.__windows:
572            if o:
573                if o.go_pprev(n) == -1:
574                    return -1
575
576    def go_hpprev(self, n):
577        for o in self.__windows:
578            if o:
579                if o.go_hpprev(n) == -1:
580                    return -1
581
582    def go_pnext(self, n):
583        for o in self.__windows:
584            if o:
585                if o.go_pnext(n) == -1:
586                    return -1
587
588    def go_hpnext(self, n):
589        for o in self.__windows:
590            if o:
591                if o.go_hpnext(n) == -1:
592                    return -1
593
594    def go_head(self, n):
595        for o in self.__windows:
596            if o:
597                if o.go_head(n) == -1:
598                    return -1
599
600    def go_tail(self, n):
601        for o in self.__windows:
602            if o:
603                if o.go_tail(n) == -1:
604                    return -1
605
606    def go_lhead(self):
607        for o in self.__windows:
608            if o:
609                if o.go_lhead() == -1:
610                    return -1
611
612    def go_ltail(self, n):
613        for o in self.__windows:
614            if o:
615                if o.go_ltail(n) == -1:
616                    return -1
617
618    def go_phead(self, n):
619        for o in self.__windows:
620            if o:
621                if o.go_phead(n) == -1:
622                    return -1
623
624    def go_pcenter(self):
625        for o in self.__windows:
626            if o:
627                if o.go_pcenter() == -1:
628                    return -1
629
630    def go_ptail(self, n):
631        for o in self.__windows:
632            if o:
633                if o.go_ptail(n) == -1:
634                    return -1
635
636    def go_to(self, n):
637        for o in self.__windows:
638            if o:
639                if o.go_to(n) == -1:
640                    return -1
641
642    def get_bytes_per_line(self):
643        return self.__bpl
644
645    def set_bytes_per_line(self, bpl):
646        assert isinstance(bpl, int)
647        assert self.__bpl > 0
648        if bpl == -1: # possible on container init
649            return -1
650        self.__bpl = bpl
651
652    def get_capacity(self):
653        return self.__def_bwindow.get_capacity()
654
655    def has_geom(self, y, x):
656        for o in self.__windows: # virtual window has geom
657            if o.has_geom(y, x):
658                return True
659        return False
660
661    def get_geom_pos(self, y, x):
662        for o in self.__windows: # virtual window has geom
663            pos = o.get_geom_pos(y, x)
664            if pos != -1:
665                return pos
666        return -1
667