View source with raw comments or as raw
   1/*  Part of XPCE --- The SWI-Prolog GUI toolkit
   2
   3    Author:        Jan Wielemaker and Anjo Anjewierden
   4    E-mail:        jan@swi.psy.uva.nl
   5    WWW:           http://www.swi.psy.uva.nl/projects/xpce/
   6    Copyright (c)  2003-2011, University of Amsterdam
   7    All rights reserved.
   8
   9    Redistribution and use in source and binary forms, with or without
  10    modification, are permitted provided that the following conditions
  11    are met:
  12
  13    1. Redistributions of source code must retain the above copyright
  14       notice, this list of conditions and the following disclaimer.
  15
  16    2. Redistributions in binary form must reproduce the above copyright
  17       notice, this list of conditions and the following disclaimer in
  18       the documentation and/or other materials provided with the
  19       distribution.
  20
  21    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  22    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  23    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
  24    FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
  25    COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
  26    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
  27    BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  28    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  29    CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  30    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
  31    ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  32    POSSIBILITY OF SUCH DAMAGE.
  33*/
  34
  35:- module(tabbed_window, []).
  36:- use_module(library(pce)).
  37:- use_module(library(hyper)).
  38
  39/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  40This class creates a tabbed window:  a   window  displaying  a number of
  41tabs, each displaying a window.   Here is some simple code using it:
  42
  43test :-
  44        new(TW, tabbed_window('Nice tabs')),
  45        send(TW, append, new(P, picture)),
  46        send(P, display, box(200, 200), point(50,50)),
  47        send(TW, append, new(view)),
  48        send(TW, append, new(D, dialog)),
  49        send(D, append, text_item(name)),
  50        send(D, append, button(quit, message(TW, destroy))),
  51        send(TW, open).
  52- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
  53
  54
  55:- pce_begin_class(tabbed_window, dialog,
  56                   "Resizeable window holding set of tabs").
  57
  58variable(label_popup,   popup*, both, "Popup shown on labels").
  59
  60initialise(W, Label:label=[name], Size:size=[size],
  61           Display:display=[display]) :->
  62    send_super(W, initialise, Label, Size, Display),
  63    send(W, hor_stretch, 100),
  64    send(W, ver_stretch, 100),
  65    send(W, hor_shrink, 100),
  66    send(W, ver_shrink, 100),
  67    send(W, pen, 0),
  68    send(W, border, size(0,0)),
  69    send_super(W, append, new(tab_stack)).
  70
  71resize(W, Tab:[tab]) :->
  72    "Resize member tabs to fit the dialog"::
  73    get_super(W, member, tab_stack, TS),
  74    get(W, area, area(_,_,Width, Height)),
  75    new(LabelH, number(0)),
  76    send(TS?graphicals, for_all,
  77         message(LabelH, maximum, @arg1?label_size?height)),
  78    get(LabelH, value, LH),
  79    TabH is Height - LH,
  80    (   Tab == @default
  81    ->  send(TS?graphicals, for_all,
  82             message(@arg1, size, size(Width,TabH)))
  83    ;   send(Tab, size, size(Width,TabH))
  84    ).
  85
  86layout_dialog(W, _Gap:[size], _Size:[size], _Border:[size]) :->
  87    "Overrule to deal with nested tabbed windows"::
  88    new(S0, size(0,0)),
  89    send_super(W, layout_dialog, S0, S0, S0).
  90
  91:- pce_group(stack).
  92
  93on_top(W, Top:'name|window') :->
  94    "Put the named tab or tab containing Window on top"::
  95    get_super(W, member, tab_stack, TS),
  96    (   atom(Top)
  97    ->  (   get(TS, member, Top, Tab)
  98        ->  send(TS, on_top, Tab)
  99        ;   get(W, hypered, tab, @arg3?name == Top, Window)
 100        ->  send(Window, expose)
 101        )
 102    ;   get(Top, container, window_tab, Tab)
 103    ->  send(TS, on_top, Tab)
 104    ).
 105
 106
 107current(W, Window:window) :<-
 108    "Window of currently selected tab"::
 109    get_super(W, member, tab_stack, TS),
 110    get(TS, on_top, Tab),
 111    get(Tab, window, Window).
 112
 113current(W, Window:window) :->
 114    "Window of currently selected tab"::
 115    get(Window, container, window_tab, Tab),
 116    (   get(Tab, status, on_top)
 117    ->  send(W, resize, Tab)
 118    ;   get_super(W, member, tab_stack, TS),
 119        send(TS, on_top, Tab)
 120    ).
 121
 122:- pce_group(members).
 123
 124%       ->append: Window, Label, [Expose]
 125%
 126%       Append a new tab using Window with the given tab label.
 127%
 128%       The call to ->'_compute_desired_size' should be properly delayed
 129%       until the tabbed window is actually   created,  but this doesn't
 130%       appear to work properly. If Expose == @on the tab is immediately
 131%       brought to the top.
 132
 133append(W, Window:window=window, Label:name=[name], Expose:expose=[bool]) :->
 134    "Append a window to the tabs"::
 135    send(Window, '_compute_desired_size'),
 136    send(W, tab, new(Tab, window_tab(Window, Label))),
 137    (   Expose == @on
 138    ->  send(W, resize, Tab),
 139        get_super(W, member, tab_stack, TS),
 140        send(TS, on_top, Tab)
 141    ;   true
 142    ).
 143
 144member(W, Name:name, Window:window) :<-
 145    "Get named window from tabbed window"::
 146    get_super(W, member, tab_stack, TS),
 147    get(TS, member, Name, Tab),
 148    get(Tab, window, Window).
 149
 150members(W, Windows:chain) :<-
 151    "New chain with member windows"::
 152    new(Windows, chain),
 153    get_super(W, member, tab_stack, TS),
 154    send(TS?graphicals, for_all,
 155         message(Windows, append, @arg1?window)),
 156    (   get(W, all_hypers, Hypers)
 157    ->  send(Hypers, for_all,
 158             if(@arg1?forward_name == toplevel,
 159                message(Windows, append, @arg1?to)))
 160    ;   true
 161    ).
 162
 163clear(W) :->
 164    "Remove all member tabs"::
 165    get_super(W, member, tab_stack, TS),
 166    send(TS, clear).
 167
 168tab(W, Tab:tab) :->
 169    "Add normal tab"::
 170    get_super(W, member, tab_stack, TS),
 171    send(TS, append, Tab),
 172    (   get(W, is_displayed, @on)
 173    ->  send(W, resize, Tab)
 174    ;   true
 175    ).
 176
 177tab(W, Name:name, Tab:tab) :<-
 178    "Find named tab"::
 179    get_super(W, member, tab_stack, TS),
 180    get(TS, member, Name, Tab).
 181
 182empty(_W) :->
 183    "Abstract method.  Called if last window disappears"::
 184    true.
 185
 186:- pce_group(frame).
 187
 188frame_window(TW, Window:window, Name:name, Rank:'1..', Frame:frame) :<-
 189    "After un-tabbing, give the window a new frame"::
 190    new(Frame, window_tab_frame(Window, Name, Rank)),
 191    new(_, partof_hyper(TW, Window, toplevel, tab)).
 192
 193:- pce_end_class(tabbed_window).
 194
 195
 196                 /*******************************
 197                 *           WINDOW TAB         *
 198                 *******************************/
 199
 200
 201:- pce_begin_class(window_tab(name), tab,
 202                   "Tab displaying a window").
 203
 204variable(window,        window*,      get, "Displayed window").
 205variable(closing,       bool := @off, get, "We are about to close").
 206delegate_to(window).
 207
 208initialise(T, Window:window=[window], Name:name=[name]) :->
 209    "Create from window and name"::
 210    (   Window == @default
 211    ->  new(W, picture)
 212    ;   W = Window
 213    ),
 214    (   Name == @default
 215    ->  get(W, name, TheName)
 216    ;   TheName = Name
 217    ),
 218    (   get(W, decoration, Decor),
 219        Decor \== @nil
 220    ->  true
 221    ;   Decor = Window
 222    ),
 223    send(Decor, lock_object, @on),
 224    (   get(Decor, slot, frame, Frame),
 225        Frame \== @nil
 226    ->  send(Frame, delete, Decor)
 227    ;   true
 228    ),
 229    send(Decor, slot, tile, @nil),
 230    send_super(T, initialise, TheName),
 231    send(T, border, size(0,0)),
 232    send_super(T, display, Decor),
 233    get(Decor, unlock, _),
 234    send(T, slot, window, W),
 235    new(_, mutual_dependency_hyper(T, W, window, tab)).
 236
 237unlink(Tab) :->
 238    "Trap if I'm the last tab"::
 239    (   get(Tab, device, Dev),
 240        Dev \== @nil
 241    ->  get(Dev?graphicals, size, Count),
 242        (   Count == 1
 243        ->  get(Tab, container, tabbed_window, TabbedWindow),
 244            send_super(Tab, unlink),
 245            send(TabbedWindow, empty)
 246        ;   send_super(Tab, unlink)
 247        )
 248    ;   send_super(Tab, unlink)
 249    ).
 250
 251:- pce_group(resize).
 252
 253%       ->size
 254%
 255%       This method must update the size of  the window. For some, to me
 256%       unknown,  reason  this  does  not    work  correctly  when  done
 257%       immediately.  Possibly  this  has  something   to  do  with  X11
 258%       synchronisation. We use the hack   in_pce_thread/1 to reschedule
 259%       the window resize in the event loop.
 260
 261size(T, Size:size) :->
 262    "Adjust size of tab and window"::
 263    (   get(T, closing, @on)
 264    ->  true
 265    ;   in_pce_thread(send(T, resize_window)),
 266        send_super(T, size, Size)
 267    ).
 268
 269resize_window(T) :->
 270    get(T, size, size(W, H)),
 271    get(T, window, Window),
 272    (   get(Window, decoration, Decor),
 273        Decor \== @nil
 274    ->  Resize = Decor
 275    ;   Resize = Window
 276    ),
 277    send(Resize, do_set, 0,0,W,H).
 278
 279:- pce_group(event).
 280
 281status(T, Status:{on_top,hidden}) :->
 282    send_super(T, status, Status),
 283    (   Status == on_top,
 284        get(T, is_displayed, @on),
 285        get(T, container, tabbed_window, TabbedWindow)
 286    ->  send(TabbedWindow, current, T?window)
 287    ;   true
 288    ).
 289
 290:- pce_group(delegate).
 291
 292display(T, Gr:graphical, Pos:[point]) :->
 293    "Delegate to window"::
 294    get(T, window, Window),
 295    send(Window, display, Gr, Pos).
 296
 297append(T, Item:graphical, RelPos:[{below,right,next_row}]) :->
 298    "Delegate to window"::
 299    get(T, window, Window),
 300    send(Window, append, Item, RelPos).
 301
 302:- pce_group(event).
 303
 304label_popup(Tab, Popup:popup) :<-
 305    "Get popup for label"::
 306    get_super(Tab, window, TabbedWindow),
 307    get(TabbedWindow, label_popup, Popup),
 308    Popup \== @nil.
 309
 310:- pce_global(@window_tab_label_recogniser,
 311              new(popup_gesture(@receiver?label_popup))).
 312
 313label_event(G, Ev:event) :->
 314    "Show popup on label of tab"::
 315    (   send_super(G, label_event, Ev)
 316    ->  true
 317    ;   send(@window_tab_label_recogniser, event, Ev)
 318    ).
 319
 320:- pce_group(frame).
 321
 322rank(Tab, Rank:'1..') :<-
 323    "Get position number of the tab"::
 324    get(Tab, device, Stack),
 325    get(Stack?graphicals, index, Tab, Rank).
 326
 327rank(Tab, Rank:'1..') :->
 328    "Move tab in rank"::
 329    get(Tab, device, Stack),
 330    get(Stack?graphicals, index, Tab, Rank0),
 331    (   Rank == Rank0
 332    ->  true
 333    ;   (   Rank > Rank0
 334        ->  Rank1 is Rank+1
 335        ;   Rank1 = Rank
 336        ),
 337        (   Rank1 == 1
 338        ->  send(Tab, hide)
 339        ;   Before is Rank1 - 1,
 340            get(Stack?graphicals, nth1, Before, BeforeGr)
 341        ->  send(Tab, expose, BeforeGr)
 342        ;   send(Tab, expose)               % make last one
 343        ),
 344        send(Stack, layout_labels)
 345    ).
 346
 347untab(Tab, W:window) :<-
 348    "Remove a tab from the tabbed window and return the window"::
 349    get(Tab, window, W),
 350    send(W, lock_object, @on),
 351    send(Tab, delete_hypers, window),
 352    free(Tab),
 353    get(W, unlock, _).
 354
 355untab(Tab) :->
 356    "Turn the window into a toplevel window"::
 357    get(Tab, rank, Rank),
 358    get(Tab, name, Name),
 359    get(Tab, container, dialog, TabbedWindow),
 360    get(Tab, display_position, point(X, Y)),
 361    get(Tab, untab, Window),
 362    get(TabbedWindow, frame_window, Window, Name, Rank, Frame),
 363    send(Frame, open, point(X, Y+20)).
 364
 365%       ->close_other_tabs
 366%
 367%       Close all tabs but me. To work   around scheduled resize for the
 368%       subwindows we first indicate we are about to close the tabs. See
 369%       also ->size.
 370
 371close_other_tabs(Tab) :->
 372    "Destroy all tabs except for me"::
 373    get(Tab, device, Stack),
 374    send(Stack?graphicals, for_all,
 375         if(@arg1 \== Tab,
 376            message(@arg1, slot, closing, @on))),
 377    send(Stack?graphicals, for_all,
 378         if(@arg1 \== Tab,
 379            message(@arg1, destroy))).
 380
 381:- pce_end_class(window_tab).
 382
 383
 384:- pce_begin_class(window_tab_frame, frame,
 385                   "Temporary frame for an untabbed window").
 386
 387variable(rank, '1..', get, "Saved position in tabbed window").
 388
 389initialise(F, Window:window, Name:name, Rank:'1..') :->
 390    send(F, slot, rank, Rank),
 391    send_super(F, initialise, Name?label_name),
 392    send(F, append, Window),
 393    send(F, done_message, message(F, retab)).
 394
 395
 396window(F, Window:window) :<-
 397    "Get the un-tabbed window"::
 398    get(F?members, head, Window).
 399
 400retab(F) :->
 401    "Bring the window back to its tab"::
 402    get(F, window, Window),
 403    get(Window, hypered, tab, TabbedWindow),
 404    get(F, rank, Rank),
 405    send(F, delete, Window),
 406    send(Window, delete_hypers, tab),
 407    send(TabbedWindow, append, Window),
 408    get(Window, container, tab, Tab),
 409    send(Tab, rank, Rank),
 410    send(F, destroy).
 411
 412contained_in(F, TabbedWindow:tabbed_window) :<-
 413    "An untabbed window is consider part of the tab"::
 414    get(F, window, Window),
 415    get(Window, hypered, tab, TabbedWindow).
 416
 417:- pce_end_class(window_tab_frame).