1 /*
2     This file is part of GNU APL, a free implementation of the
3     ISO/IEC Standard 13751, "Programming Language APL, Extended"
4 
5     Copyright (C) 2008-2018  Dr. Jürgen Sauermann
6 
7     This program is free software: you can redistribute it and/or modify
8     it under the terms of the GNU General Public License as published by
9     the Free Software Foundation, either version 3 of the License, or
10     (at your option) any later version.
11 
12     This program is distributed in the hope that it will be useful,
13     but WITHOUT ANY WARRANTY; without even the implied warranty of
14     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15     GNU General Public License for more details.
16 
17     You should have received a copy of the GNU General Public License
18     along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 */
20 
21 #include <errno.h>
22 #include <fcntl.h>
23 #include <poll.h>
24 #include <sys/ioctl.h>
25 
26 #include "../config.h"
27 #include "LibPaths.hh"
28 #include "PointerCell.hh"
29 #include "Quad_GTK.hh"
30 #include "Workspace.hh"
31 
32 Quad_GTK  Quad_GTK::_fun;
33 Quad_GTK * Quad_GTK::fun = &Quad_GTK::_fun;
34 
35 #if HAVE_GTK3
36 //-----------------------------------------------------------------------------
37 Token
eval_AB(Value_P A,Value_P B)38 Quad_GTK::eval_AB(Value_P A, Value_P B)
39 {
40    if (A->get_rank() > 1)   RANK_ERROR;
41    if (B->get_rank() > 1)   RANK_ERROR;
42    if (A->is_char_array() && B->is_char_array())
43       {
44          const UCS_string css_filename = A->get_UCS_ravel();
45          const UCS_string gui_filename = B->get_UCS_ravel();
46          const int fd = open_window(gui_filename, &css_filename);
47          return Token(TOK_APL_VALUE1, IntScalar(fd, LOC));
48       }
49 
50    if (!B->is_int_scalar())
51       {
52         MORE_ERROR() << "A ⎕GTK B expects B to be an integer scalar"
53                         " or a text vector";
54         DOMAIN_ERROR;
55       }
56 
57 const int function = B->get_ravel(0).get_int_value();
58 int fd = -1;
59    switch(function)
60       {
61         case 0: // close window/GUI
62              if (!A->is_int_scalar())   goto bad_fd;
63              fd = A->get_ravel(0).get_int_value();
64              return Token(TOK_APL_VALUE1, close_window(fd));
65 
66         case 3: // increase verbosity
67              if (!A->is_int_scalar())   goto bad_fd;
68              if (write_TL0(fd, 7))
69                 {
70                   CERR << "write( Tag 7) failed in Ah ⎕GTK 3";
71                   return Token(TOK_APL_VALUE1, IntScalar(-3, LOC));
72                 }
73              return Token(TOK_APL_VALUE1, IntScalar(0, LOC));
74 
75         case 4: // decrease verbosity
76              if (!A->is_int_scalar())   goto bad_fd;
77              if (write_TL0(fd, 8))
78                 {
79                   CERR << "write( Tag 8) failed in Ah ⎕GTK 3";
80                   return Token(TOK_APL_VALUE1, IntScalar(-4, LOC));
81                 }
82              return Token(TOK_APL_VALUE1, IntScalar(0, LOC));
83 
84         default: MORE_ERROR() << "Invalid function number Bi=" << function
85                               << " in A ⎕GTK Bi";
86                  DOMAIN_ERROR;
87       }
88 
89    MORE_ERROR() << "Unexpected A or B in A ⎕GTK B";
90    DOMAIN_ERROR;
91 
92 bad_fd:
93    MORE_ERROR() << "Ah ⎕GTK " << function
94                 << " expects a handle (i.e. an integer scalar) Ah";
95    DOMAIN_ERROR;
96 }
97 //-----------------------------------------------------------------------------
98 Token
eval_B(Value_P B)99 Quad_GTK::eval_B(Value_P B)
100 {
101    if (B->get_rank() > 1)   RANK_ERROR;
102    if (B->is_char_array())
103       {
104          const UCS_string gui_filename = B->get_UCS_ravel();
105          const int fd = open_window(gui_filename, /* no CSS */ 0);
106          return Token(TOK_APL_VALUE1, IntScalar(fd, LOC));
107       }
108 
109    if (!B->is_int_scalar())
110       {
111         MORE_ERROR() << "⎕GTK B expects B to be an integer scalar"
112                         " or a text vector";
113         DOMAIN_ERROR;
114       }
115 
116 const int function = B->get_ravel(0).get_int_value();
117    switch(function)
118       {
119         case 0:   // list of open fds
120              return Token(TOK_APL_VALUE1, window_list());
121 
122         case 1: // blocking poll for next event
123         case 2: // non-blocking wait for next event
124              poll_all();
125              if (function == 2 && event_queue.size() == 0)   // non-blocking
126                 return Token(TOK_APL_VALUE1, IntScalar(0, LOC));
127 
128              // blocking
129              //
130              while (event_queue.size() == 0 && !interrupt_is_raised())
131                    poll_all();
132 
133              // at this point either event_queue.size() > 0 or an interrupt
134              // was raised
135              //
136              if (event_queue.size() == 0)   // hence interrupt was raised
137                 {
138                    clear_interrupt_raised(LOC);
139                    return Token(TOK_APL_VALUE1, IntScalar(0, LOC));
140                 }
141 
142              {
143                UCS_string HWF = event_queue[0];   // fd, widget : fun
144                event_queue.erase(event_queue.begin());
145 
146                // split (Unicode)Handle,"widget:function"
147                // into a 3-element APL vector Handle (⊂"widget") (⊂"function")
148                //
149                UCS_string_vector args;
150                UCS_string arg;
151                for (ShapeItem j = 1; j < HWF.size(); ++j)
152                    {
153                      if (HWF[j] == UNI_ASCII_COLON)
154                         {
155                           args.push_back(arg);
156                           arg.clear();
157                         }
158                      else
159                         {
160                           arg.append(HWF[j]);
161                         }
162                    }
163                args.push_back(arg);
164 
165                Value_P Z(1 + args.size(), LOC);
166                new (Z->next_ravel()) IntCell(HWF[0]);
167                loop(a, args.size())
168                    {
169                      Value_P Za(args[a], LOC);
170                      new (Z->next_ravel())   PointerCell(Za.get(), Z.getref());
171                    }
172                Z->check_value(LOC);
173                return Token(TOK_APL_VALUE1, Z);
174              }
175 
176         default: MORE_ERROR() << "Invalid function number Bi=" << function
177                               << " in ⎕GTK Bi";
178                  DOMAIN_ERROR;
179       }
180 
181    MORE_ERROR() << "Unexpected B in ⎕GTK B";
182    DOMAIN_ERROR;
183 }
184 //-----------------------------------------------------------------------------
185 Token
eval_AXB(Value_P A,Value_P X,Value_P B)186 Quad_GTK::eval_AXB(Value_P A, Value_P X, Value_P B)
187 {
188    if (B->get_rank() > 1)   RANK_ERROR;
189 
190 UTF8_string window_id;                // e.g. "entry1"
191 const int fd = resolve_window(X.get(), window_id);
192    write_TLV(fd, 6, window_id);   // select wodget
193 
194 int fun = FNUM_INVALID;
195    if (B->is_int_scalar())         fun = B->get_ravel(0).get_int_value();
196    else if (B->is_char_string())   fun = resolve_fun_name(window_id, B.get());
197    else
198       {
199         MORE_ERROR() << "A ⎕GTK[X] B expects B to be an integer scalar"
200                         " or a text vector";
201         DOMAIN_ERROR;
202       }
203 
204 int command_tag = -1;
205 int response_tag = -1;
206 Gtype Atype = gtype_V;
207 
208    switch(fun)
209       {
210          case FNUM_INVALID: DOMAIN_ERROR;
211          case FNUM_0:   return Token(TOK_APL_VALUE1, IntScalar(fd, LOC));
212 
213 #define gtk_event_def(ev_ename, ...)
214 #define gtk_fun_def(glade_ID, gtk_class, gtk_function, _ZAname,_Z, A,_help) \
215          case FNUM_ ## gtk_class ## _ ## gtk_function:                      \
216               command_tag = Command_ ## gtk_class ## _ ## gtk_function;     \
217               response_tag = Response_ ## gtk_class ## _ ## gtk_function;   \
218               Atype = gtype_ ## A;                                          \
219               break;
220 #include "Gtk/Gtk_map.def"
221 
222          default:
223               MORE_ERROR() << "Bad function B in A ⎕GTK B (B='"
224                            << *B << ", fun=" << fun;
225               DOMAIN_ERROR;
226       }
227 
228    if (Atype == gtype_V)    VALENCE_ERROR;
229    if (A->get_rank() > 1)   RANK_ERROR;
230 
231 UCS_string ucs_A;
232    if (fun == FNUM_GtkDrawingArea_draw_commands)
233       {
234         loop(a, A->element_count())
235             {
236               const Cell & cell = A->get_ravel(a);
237               if (!cell.is_pointer_cell())
238                  {
239                     MORE_ERROR() << "A ⎕GTK " << fun
240                                  << " expects A to be a vector of "
241                                     "draw commands (strings)";
242                     DOMAIN_ERROR;
243                  }
244               Value_P command = cell.get_pointer_value();
245               ucs_A.append(UCS_string(*command));
246               ucs_A.append(UNI_ASCII_LF);
247             }
248       }
249    else
250       {
251         if (!A->is_char_string())
252            {
253              MORE_ERROR() << "A ⎕GTK[X] B expects A to be a text vector";
254              DOMAIN_ERROR;
255            }
256 
257         ucs_A = UCS_string(*A);
258       }
259 UTF8_string utf_A(ucs_A);
260    write_TLV(fd, command_tag, utf_A);
261    return Token(TOK_APL_VALUE1, poll_response(fd, response_tag));
262 }
263 //-----------------------------------------------------------------------------
264 Token
eval_XB(Value_P X,Value_P B)265 Quad_GTK::eval_XB(Value_P X, Value_P B)
266 {
267    if (B->get_rank() > 1)   RANK_ERROR;
268 
269 UTF8_string window_id;                // e.g. "entry1"
270 const int fd = resolve_window(X.get(), window_id);
271    write_TLV(fd, 6, window_id);   // select wodget
272 
273 int fun = FNUM_INVALID;
274    if (B->is_int_scalar())         fun = B->get_ravel(0).get_int_value();
275    else if (B->is_char_string())   fun = resolve_fun_name(window_id, B.get());
276    else                            DOMAIN_ERROR;
277 
278 int command_tag = -1;
279 int response_tag = -1;
280 Gtype Atype = gtype_V;
281 
282    switch(fun)
283       {
284         case 0:
285            write_TLV(fd, 4, window_id);
286            return Token(TOK_APL_VALUE1, IntScalar(fd, LOC));
287 
288 #define gtk_event_def(ev_ename, ...)
289 #define gtk_fun_def(glade_ID, gtk_class, gtk_function, _ZAname, _Z, A, _help) \
290          case FNUM_ ## gtk_class ## _ ## gtk_function:                        \
291               command_tag = Command_ ## gtk_class ## _ ## gtk_function;       \
292               response_tag = Response_ ## gtk_class ## _ ## gtk_function;     \
293               Atype = gtype_ ## A;                                            \
294               break;
295 #include "Gtk/Gtk_map.def"
296       }
297 
298    if (Atype != gtype_V)   VALENCE_ERROR;
299 
300    write_TL0(fd, command_tag);   // command
301    return Token(TOK_APL_VALUE1, poll_response(fd, response_tag));
302 }
303 //-----------------------------------------------------------------------------
304 Value_P
read_fd(int fd,int tag)305 Quad_GTK::read_fd(int fd, int tag)
306 {
307    // tag == -1 indicates a poll for ANY tag (= an event as opposed to a
308    // command response). In that case the poll is non-blocking.
309 
310 char TLV[1000];
311 
312 const ssize_t rx_len = read(fd, TLV, sizeof(TLV));
313 
314    if (rx_len < 8)
315       {
316         MORE_ERROR() << "read() failed in Quad_GTK::read_fd(): "
317                      << strerror(errno);
318         DOMAIN_ERROR;
319       }
320 
321 const int TLV_tag = (TLV[0] & 0xFF) << 24 | (TLV[1] & 0xFF) << 16
322                   | (TLV[2] & 0xFF) <<  8 | (TLV[3] & 0xFF);
323 
324 
325 const int V_len = (TLV[4] & 0xFF) << 24 | (TLV[5] & 0xFF) << 16
326                 | (TLV[6] & 0xFF) <<  8 | (TLV[7] & 0xFF);
327 
328    Assert(rx_len == (V_len + 8));
329 
330 char * V = TLV + 8;
331    V[V_len] = 0;
332 
333    // 1. check for events from generic_callback()
334    //
335    if (TLV_tag == Event_widget_fun ||            // "H:button1:clicked"
336        TLV_tag == Event_widget_fun_id_class ||   // dito + :id:class
337        TLV_tag == Event_toplevel_window_done)
338       {
339         // V is a string of the form H%s:%s:%s:% where H is a placeholder
340         // for the fd over which we have received V.
341         //
342         // replace H by the actual fd and store the result in event_queue.
343         //
344         UTF8_string data_utf;   // H:widget:callback
345         loop(v, V_len)   data_utf += V[v];
346         UCS_string data_ucs(data_utf);
347         data_ucs[0] = (Unicode(fd));
348 
349         event_queue.push_back(data_ucs);
350 
351         return Value_P();   // i.e. NULL
352       }
353 
354    if (tag == -1)   // not waiting for a specific tag
355       {
356         CERR << "*** ⎕GTK: ignoring event: " << V << endl;
357         return Value_P();   // i.e. NULL
358       }
359 
360    if (TLV_tag != tag)
361       {
362         CERR << "Got unexpected tag " << TLV_tag
363              << " when waiting for response " << tag << endl;
364         return Value_P();
365       }
366 
367    // TLV_tag is the expected one. Strip off the response offset
368    // Response_0 (= 2000) from the TLV_tag to get the function number
369    // and return APL vector function,"result-value"
370    //
371 UTF8_string utf;
372    loop(u, V_len)   utf += V[u];
373    UCS_string ucs(utf);
374 
375 Value_P Z(1 + ucs.size(), LOC);
376    new (Z->next_ravel())   IntCell(TLV_tag - Response_0);
377    loop(u, ucs.size())   new (Z->next_ravel())   CharCell(ucs[u]);
378    Z->check_value(LOC);
379    return  Z;
380 }
381 //-----------------------------------------------------------------------------
382 void
poll_all()383 Quad_GTK::poll_all()
384 {
385 const int count = open_windows.size();
386 pollfd fds[count];
387    loop(w, count)
388        {
389          fds[w].fd = open_windows[w].fd;
390          fds[w].events = POLLIN | POLLHUP;
391          fds[w].revents = 0;
392        }
393 
394 const int ready = poll(fds, count, 0);
395    if (ready == 0)   return;
396    if (ready > 0)
397       {
398         loop(w, count)
399             {
400               if (fds[w].revents)   read_fd(fds[w].fd, -1);
401              }
402       }
403    else
404       {
405         if (errno == EINTR)   return;   // user hits ^C
406         CERR << "*** poll() failed: " << strerror(errno) << endl;
407       }
408 }
409 //-----------------------------------------------------------------------------
410 Value_P
poll_response(int fd,int tag)411 Quad_GTK::poll_response(int fd, int tag)
412 {
413 again:
414 
415 pollfd pfd;
416    pfd.fd = fd;
417    pfd.events = POLLIN | POLLHUP;
418    pfd.revents = 0;
419 
420 const int ready = poll(&pfd, 1, 0);
421    if (ready == 0)
422       {
423         usleep(100000);
424         goto again;
425       }
426 
427    if (ready > 0 && pfd.revents)
428       {
429         Value_P Z = read_fd(fd, tag);
430         if (!Z)   goto again;
431         return Z;
432       }
433 
434    CERR << "*** poll() failed" << endl;
435    DOMAIN_ERROR;
436 }
437 //-----------------------------------------------------------------------------
438 Value_P
window_list() const439 Quad_GTK::window_list() const
440 {
441 Value_P Z(open_windows.size(), LOC);
442    loop(w, open_windows.size())
443       {
444         const APL_Integer fd = open_windows[w].fd;
445         new (Z->next_ravel()) IntCell(fd);
446       }
447 
448    Z->set_default_Int();
449    Z->check_value(LOC);
450    return Z;
451 }
452 //-----------------------------------------------------------------------------
453 int
resolve_window(const Value * X,UTF8_string & window_id)454 Quad_GTK::resolve_window(const Value * X, UTF8_string & window_id)
455 {
456    if (X->get_rank() > 1)   RANK_ERROR;
457 const int fd = X->get_ravel(0).get_int_value();
458 
459    // verify that ↑X is an open window...
460    //
461 bool window_valid = false;
462    loop(w, open_windows.size())
463        {
464          if (fd == open_windows[w].fd)
465             {
466               window_valid = true;
467               break;
468             }
469        }
470 
471    if (!window_valid)
472       {
473          MORE_ERROR() << "Invalid window " << fd
474                       << " in Quad_GTK::resolve_window()";
475          DOMAIN_ERROR;
476       }
477 
478    loop(i, X->element_count())
479        {
480          if (i)   window_id += X->get_ravel(i).get_char_value();
481        }
482 
483    return fd;
484 }
485 //-----------------------------------------------------------------------------
486 Quad_GTK::Fnum
resolve_fun_name(UTF8_string & window_id,const Value * B)487 Quad_GTK::resolve_fun_name(UTF8_string & window_id, const Value * B)
488 {
489    // window_id is a class and an instance number, such as entry1
490    // Determine the length of the class prefixx
491    //
492 int wid_len = window_id.size();
493    while (wid_len &&
494           window_id[wid_len - 1] >= '0' &&
495           window_id[wid_len - 1] <= '9')   --wid_len;
496 
497 const UCS_string ucs_B(*B);
498 UTF8_string utf_B(ucs_B);
499 const char * wid_class = window_id.c_str();
500 const char * fun_name = utf_B.c_str();
501 
502 #define gtk_event_def(ev_ename, ...)
503 #define gtk_fun_def(glade_ID, gtk_class, gtk_function, _ZAname,_Z,_A,_help) \
504    if (!(strncmp(wid_class, #glade_ID, wid_len) ||                          \
505          strcmp(fun_name,   #gtk_function)))                                 \
506       return FNUM_ ## gtk_class ## _ ## gtk_function;
507 
508 #include "Gtk/Gtk_map.def"
509 
510    MORE_ERROR() << "function string class=" << wid_class
511         << ", function=" << fun_name << " could not be resolved";
512    return FNUM_INVALID;
513 }
514 //-----------------------------------------------------------------------------
515 void
clear()516 Quad_GTK::clear()
517 {
518    loop(w, open_windows.size())   close_window(open_windows[w].fd);
519 }
520 //-----------------------------------------------------------------------------
521 Value_P
close_window(int fd)522 Quad_GTK::close_window(int fd)
523 {
524    loop(w, open_windows.size())
525       {
526         window_entry & we = open_windows[w];
527         if (fd == we.fd)
528            {
529              we = open_windows.back();
530              open_windows.pop_back();
531              if (write_TL0(fd, 5))
532                 {
533                   CERR << "write(close Tag) failed in ⎕GTK::close_window()";
534                 }
535              const int err = Quad_FIO::fun->close_handle(fd);
536              return IntScalar(err, LOC);
537            }
538       }
539 
540    MORE_ERROR() << "Invalid ⎕GTK handle " << fd;
541    DOMAIN_ERROR;
542 }
543 //-----------------------------------------------------------------------------
544 int
write_TL0(int fd,int tag)545 Quad_GTK::write_TL0(int fd, int tag)
546 {
547 unsigned char TLV[8];
548    memset(TLV, 0, 8);
549    TLV[0] = tag >> 24;
550    TLV[1] = tag >> 16;
551    TLV[2] = tag >> 8;
552    TLV[3] = tag;
553 
554     if (write(fd, TLV, 8) != 8)
555        {
556          CERR << "write(Tag = " << tag << ") failed in ⎕GTK::write_TL0()";
557          return -1;
558        }
559 
560    return 0;
561 }
562 //-----------------------------------------------------------------------------
563 int
write_TLV(int fd,int tag,const UTF8_string & value)564 Quad_GTK::write_TLV(int fd, int tag, const UTF8_string & value)
565 {
566 const int TLV_len = 8 + value.size();
567 unsigned char TLV[TLV_len];
568    TLV[0] = tag >> 24 & 0xFF;
569    TLV[1] = tag >> 16 & 0xFF;
570    TLV[2] = tag >> 8  & 0xFF;
571    TLV[3] = tag       & 0xFF;
572    TLV[4] = value.size() >> 24;
573    TLV[5] = value.size() >> 16;
574    TLV[6] = value.size() >>  8;
575    TLV[7] = value.size();
576    loop(s, value.size())   TLV[8 + s] = value[s];
577 
578     if (write(fd, TLV, TLV_len) != TLV_len)
579        {
580          CERR << "write(Tag = " << tag << ") failed in ⎕GTK::write_TLV()";
581          return -1;
582        }
583 
584    return 0;
585 }
586 //-----------------------------------------------------------------------------
587 int
open_window(const UCS_string & gui_filename,const UCS_string * css_filename)588 Quad_GTK::open_window(const UCS_string & gui_filename,
589                    const UCS_string * css_filename)
590 {
591    // locate the Gtk_server. It should  live in one of two places:
592    //
593    // 1, for an installed apl: in the same directory as apl, or
594    // 2. during development in subdir Gtk of the src directory
595    //
596 char path1[APL_PATH_MAX + 1 + 8];
597 char path2[APL_PATH_MAX + 1 + 8];
598 const char * bin_dir = LibPaths::get_APL_bin_path();
599 int slen = snprintf(path1, APL_PATH_MAX, "%s/Gtk/Gtk_server", bin_dir);
600    if (slen >= APL_PATH_MAX)   path1[APL_PATH_MAX] = 0;
601    slen = snprintf(path2, APL_PATH_MAX, "%s/Gtk_server", bin_dir);
602    if (slen >= APL_PATH_MAX)   path2[APL_PATH_MAX] = 0;
603 
604 const char * path = 0;
605    if (!access(path1, X_OK))        path = path1;
606    else if (!access(path2, X_OK))   path = path2;
607    else
608       {
609         MORE_ERROR() << "No Gtk_server found in " << path1
610                      << "\nor " << path2;
611         DOMAIN_ERROR;
612       }
613 
614 const int fd = Quad_FIO::fun->do_FIO_57(path);
615 
616    // write TLVs 1 and 3 or 1, 2, and 3 to Gtk_server...
617    //
618 UTF8_string gui_utf8(gui_filename);
619    slen = snprintf(path1 + 8, APL_PATH_MAX, "%s", gui_utf8.c_str());
620    if (slen >= APL_PATH_MAX)   path1[APL_PATH_MAX] = 0;
621    memset(path1, 0, 8);
622    path1[3] = 1;   // tag = 1: gui filename
623    path1[6] = gui_utf8.size() >> 8;
624    path1[7] = gui_utf8.size() & 0xFF;
625    slen = write(fd, path1, 8 + gui_utf8.size());
626    if (slen == -1)
627       {
628          Quad_FIO::fun->close_handle(fd);
629          MORE_ERROR() << "write(Tag 1) failed in ⎕GTK";
630          DOMAIN_ERROR;
631       }
632 
633    if (css_filename)
634       {
635         UTF8_string css_utf8(*css_filename);
636         memset(path2, 0, 8);
637         slen = snprintf(path2 + 8, APL_PATH_MAX, "%s", css_utf8.c_str());
638         path2[3] = 2;   // tag = 2: css filename
639         path2[6] = css_utf8.size() >> 8;
640         path2[7] = css_utf8.size() & 0xFF;
641         slen = write(fd, path2, 8 + css_utf8.size());
642         if (slen == -1)
643            {
644               Quad_FIO::fun->close_handle(fd);
645               MORE_ERROR() << "write(Tag 2) failed in ⎕GTK";
646               DOMAIN_ERROR;
647            }
648       }
649 
650    if (write_TL0(fd, 3))
651       {
652          Quad_FIO::fun->close_handle(fd);
653          MORE_ERROR() << "write(Tag 3) failed in ⎕GTK";
654          DOMAIN_ERROR;
655       }
656 
657 window_entry we = { fd };
658    open_windows.push_back(we);
659    return fd;
660 }
661 //-----------------------------------------------------------------------------
662 
663 #else // not HAVE_GTK3
664 
665 //-----------------------------------------------------------------------------
666 Token
eval_AB(Value_P A,Value_P B)667 Quad_GTK::eval_AB(Value_P A, Value_P B)
668 {
669    MORE_ERROR() << "libgtk+ version 3 was not found at ./configure time";
670    DOMAIN_ERROR;
671 }
672 //-----------------------------------------------------------------------------
673 Token
eval_B(Value_P B)674 Quad_GTK::eval_B(Value_P B)
675 {
676    MORE_ERROR() << "libgtk+ version 3 was not found at ./configure time";
677    DOMAIN_ERROR;
678 }
679 //-----------------------------------------------------------------------------
680 Token
eval_AXB(Value_P A,Value_P X,Value_P B)681 Quad_GTK::eval_AXB(Value_P A, Value_P X, Value_P B)
682 {
683    MORE_ERROR() << "libgtk+ version 3 was not found at ./configure time";
684    DOMAIN_ERROR;
685 }
686 //-----------------------------------------------------------------------------
687 Token
eval_XB(Value_P X,Value_P B)688 Quad_GTK::eval_XB(Value_P X, Value_P B)
689 {
690    MORE_ERROR() << "libgtk+ version 3 was not found at ./configure time";
691    DOMAIN_ERROR;
692 }
693 //-----------------------------------------------------------------------------
694 void
clear()695 Quad_GTK::clear()
696 {
697 }
698 //-----------------------------------------------------------------------------
699 #endif   // HAVE_GTK3
700 
701 
702