Add context menu to ICS console XB-GTK
authorH.G. Muller <h.g.muller@hccnet.nl>
Mon, 3 Feb 2014 18:45:44 +0000 (19:45 +0100)
committerH.G. Muller <h.g.muller@hccnet.nl>
Sun, 2 Mar 2014 17:48:55 +0000 (18:48 +0100)
A right-click handler is added to the output memos of the ICS Interaction
window. It pops up the ICS text menu. It is remembered whether the menu
was already up; if not, it will be popped down after a command is selected
from it. Like the WinBoard context menu, the commands will be able to use
the clicked word.
The -icsMenu 'command' $chat is now recognized as a special case, not to
be sent to the ICS, but executed by XBoard. It will assign one of the
five chats to the clicked name. If no empty chat is available, the last
chat will be used (but not cleared).
 The -icsMenu in the master config file is now configured to contain
this command, but unfortunately this will not become effective for users
that already have a settings file.
The Text Menu now puts $input commands in ICS Console Input field when the
ICS Input Box is not up.
 The text placed in the input field for completion (as per $input directive)
turned out to be selected in GTK, so that when you stated typing, it was
erased again!
 By making the Text menu dialog subject to WindowPlacement control,
and interrogating the poition of the ICS Console window, the Text Menu
can be popped up such that the mouse pointer is on the bottom-left button.

common.h
dialogs.c
dialogs.h
gtk/xboard.c
gtk/xoptions.c
xaw/xboard.c
xboard.conf.in

index 8cd93ce..2f66365 100644 (file)
--- a/common.h
+++ b/common.h
@@ -856,6 +856,7 @@ extern WindowPlacement wpEvalGraph;
 extern WindowPlacement wpMoveHistory;
 extern WindowPlacement wpGameList;
 extern WindowPlacement wpTags;
 extern WindowPlacement wpMoveHistory;
 extern WindowPlacement wpGameList;
 extern WindowPlacement wpTags;
+extern WindowPlacement wpTextMenu;
 
 #define MAXENGINES 2000
 
 
 #define MAXENGINES 2000
 
index af8435a..16d4d19 100644 (file)
--- a/dialogs.c
+++ b/dialogs.c
@@ -966,20 +966,35 @@ BoardOptionsProc ()
 
 Option textOptions[100];
 static void PutText P((char *text, int pos));
 
 Option textOptions[100];
 static void PutText P((char *text, int pos));
+static void NewChat P((char *name));
+static char clickedWord[MSG_SIZ], click;
 
 void
 SendString (char *p)
 {
 
 void
 SendString (char *p)
 {
-    char buf[MSG_SIZ], *q;
+    char buf[MSG_SIZ], buf2[MSG_SIZ], *q;
+    if(q = strstr(p, "$name")) { // in Xaw this is already intercepted
+       if(!shellUp[TextMenuDlg] || !clickedWord[0]) return;
+       strncpy(buf2, p, MSG_SIZ);
+       snprintf(buf2 + (q-p), MSG_SIZ -(q-p), "%s%s", clickedWord, q+5);
+        p = buf2;
+    }
+    if(!strcmp(p, "$chat")) { // special case for opening chat
+        NewChat(clickedWord);
+    } else
     if(q = strstr(p, "$input")) {
        if(!shellUp[TextMenuDlg]) return;
        strncpy(buf, p, MSG_SIZ);
        strncpy(buf + (q-p), q+6, MSG_SIZ-(q-p));
        PutText(buf, q-p);
     if(q = strstr(p, "$input")) {
        if(!shellUp[TextMenuDlg]) return;
        strncpy(buf, p, MSG_SIZ);
        strncpy(buf + (q-p), q+6, MSG_SIZ-(q-p));
        PutText(buf, q-p);
-       return;
+    } else {
+       snprintf(buf, MSG_SIZ, "%s\n", p);
+       SendToICS(buf);
+    }
+    if(click) { // popped up by memo click
+       click = clickedWord[0] = 0;
+       PopDown(TextMenuDlg);
     }
     }
-    snprintf(buf, MSG_SIZ, "%s\n", p);
-    SendToICS(buf);
 }
 
 void
 }
 
 void
@@ -1267,21 +1282,6 @@ IcsKey (int n)
     SetInsertPos(&boxOptions[INPUT], strlen(val));
 }
 
     SetInsertPos(&boxOptions[INPUT], strlen(val));
 }
 
-static void
-PutText (char *text, int pos)
-{
-    char buf[MSG_SIZ], *p;
-
-    if(strstr(text, "$add ") == text) {
-       GetWidgetText(&boxOptions[INPUT], &p);
-       snprintf(buf, MSG_SIZ, "%s%s", p, text+5); text = buf;
-       pos += strlen(p) - 5;
-    }
-    SetWidgetText(&boxOptions[INPUT], text, TextMenuDlg);
-    SetInsertPos(&boxOptions[INPUT], pos);
-    HardSetFocus(&boxOptions[INPUT]);
-}
-
 void
 ICSInputBoxPopUp ()
 {
 void
 ICSInputBoxPopUp ()
 {
@@ -1706,7 +1706,7 @@ PromotionPopUp (char choice)
 
 //---------------------------- Chat Windows ----------------------------------------------
 
 
 //---------------------------- Chat Windows ----------------------------------------------
 
-static char *line, *memo, *partner, *texts[MAX_CHAT], dirty[MAX_CHAT];
+static char *line, *memo, *chatMemo, *partner, *texts[MAX_CHAT], dirty[MAX_CHAT];
 static int activePartner, hidden = 1;
 
 void ChatSwitch P((int n));
 static int activePartner, hidden = 1;
 
 void ChatSwitch P((int n));
@@ -1720,6 +1720,34 @@ int  ChatOK P((int n));
 
 void PaneSwitch P((void));
 
 
 void PaneSwitch P((void));
 
+WindowPlacement wpTextMenu;
+
+int
+ContextMenu (Option *opt, int button, int x, int y, char *text, int index)
+{ // callback for ICS-output clicks; handles button 3, passes on other events
+  char *start, *end;
+  int h;
+  if(button == -3) return TRUE; // supress default GTK context menu on up-click
+  if(button != 3) return FALSE;
+  start = end = text + index; // figure out what text was clicked
+  while(isalnum(*end)) end++;
+  while(start > text && isalnum(start[-1])) start--;
+  clickedWord[0] = NULLCHAR;
+  if(end-start >= 80) end = start + 80; // intended for small words and numbers
+  strncpy(clickedWord, start, end-start); clickedWord[end-start] = NULLCHAR;
+  click = !shellUp[TextMenuDlg]; // request auto-popdown of textmenu when we popped it up
+  h = wpTextMenu.height; // remembered height of text menu
+  if(h <= 0) h = 65;     // when not available, position w.r.t. top
+  GetPlacement(ChatDlg, &wpTextMenu);
+  if(opt->target == (void*) &chatMemo) wpTextMenu.y += (wpTextMenu.height - 30)/2; // click in chat
+  wpTextMenu.x += x - 50; wpTextMenu.y += y - h + 50; // request positioning
+  if(wpTextMenu.x < 0) wpTextMenu.x = 0;
+  if(wpTextMenu.y < 0) wpTextMenu.y = 0;
+  wpTextMenu.width = wpTextMenu.height = -1;
+  IcsTextProc();
+  return TRUE;
+}
+
 Option chatOptions[] = {
 {  0,  0,   0, NULL, NULL, "", NULL, Label , N_("Chats:") },
 { 1, SAME_ROW|TT, 75, NULL, (void*) &ChatSwitch, NULL, NULL, Button, N_("New Chat") },
 Option chatOptions[] = {
 {  0,  0,   0, NULL, NULL, "", NULL, Label , N_("Chats:") },
 { 1, SAME_ROW|TT, 75, NULL, (void*) &ChatSwitch, NULL, NULL, Button, N_("New Chat") },
@@ -1727,16 +1755,35 @@ Option chatOptions[] = {
 { 3, SAME_ROW|TT, 75, NULL, (void*) &ChatSwitch, NULL, NULL, Button, N_("New Chat") },
 { 4, SAME_ROW|TT, 75, NULL, (void*) &ChatSwitch, NULL, NULL, Button, N_("New Chat") },
 { 5, SAME_ROW|TT, 75, NULL, (void*) &ChatSwitch, NULL, NULL, Button, N_("New Chat") },
 { 3, SAME_ROW|TT, 75, NULL, (void*) &ChatSwitch, NULL, NULL, Button, N_("New Chat") },
 { 4, SAME_ROW|TT, 75, NULL, (void*) &ChatSwitch, NULL, NULL, Button, N_("New Chat") },
 { 5, SAME_ROW|TT, 75, NULL, (void*) &ChatSwitch, NULL, NULL, Button, N_("New Chat") },
-{ 250, T_VSCRL | T_FILL | T_WRAP | T_TOP,    510, NULL, (void*) &memo, NULL, NULL, TextBox, "" },
+{ 250, T_VSCRL | T_FILL | T_WRAP | T_TOP,    510, NULL, (void*) &memo, NULL, (void*) &ContextMenu, TextBox, "" },
 {  0,  0,   0, NULL, NULL, "", NULL, Break , "" },
 { 0,   T_TOP,    100, NULL, (void*) &partner, NULL, NULL, TextBox, N_("Chat partner:") },
 {  0, SAME_ROW, 0, NULL, (void*) &PaneSwitch, NULL, NULL, Button, N_("Hide") },
 {  0,  0,   0, NULL, NULL, "", NULL, Break , "" },
 { 0,   T_TOP,    100, NULL, (void*) &partner, NULL, NULL, TextBox, N_("Chat partner:") },
 {  0, SAME_ROW, 0, NULL, (void*) &PaneSwitch, NULL, NULL, Button, N_("Hide") },
-{ 250, T_VSCRL | T_FILL | T_WRAP | T_TOP,    510, NULL, (void*) &memo, NULL, NULL, TextBox, "" },
+{ 250, T_VSCRL | T_FILL | T_WRAP | T_TOP,    510, NULL, (void*) &chatMemo, NULL, (void*) &ContextMenu, TextBox, "" },
 {  0,  0,   0, NULL, NULL, "", NULL, Break , "" },
 {  0,    0,  510, NULL, (void*) &line, NULL, NULL, TextBox, "" },
 { 0, NO_OK|SAME_ROW, 0, NULL, (void*) &ChatOK, NULL, NULL, EndMark , "" }
 };
 
 {  0,  0,   0, NULL, NULL, "", NULL, Break , "" },
 {  0,    0,  510, NULL, (void*) &line, NULL, NULL, TextBox, "" },
 { 0, NO_OK|SAME_ROW, 0, NULL, (void*) &ChatOK, NULL, NULL, EndMark , "" }
 };
 
+static void
+PutText (char *text, int pos)
+{
+    char buf[MSG_SIZ], *p;
+    DialogClass dlg = ChatDlg;
+    Option *opt = &chatOptions[CHAT_IN];
+
+    if(strstr(text, "$add ") == text) {
+       GetWidgetText(&boxOptions[INPUT], &p);
+       snprintf(buf, MSG_SIZ, "%s%s", p, text+5); text = buf;
+       pos += strlen(p) - 5;
+    }
+    if(shellUp[InputBoxDlg]) opt = &boxOptions[INPUT], dlg = InputBoxDlg; // for the benefit of Xaw give priority to ICS Input Box
+    SetWidgetText(opt, text, dlg);
+    SetInsertPos(opt, pos);
+    HardSetFocus(opt);
+    CursorAtEnd(opt);
+}
+
 void
 IcsHist (int n, Option *opt, DialogClass dlg)
 {   // [HGM] input: let up-arrow recall previous line from history
 void
 IcsHist (int n, Option *opt, DialogClass dlg)
 {   // [HGM] input: let up-arrow recall previous line from history
@@ -1783,7 +1830,7 @@ ChatOK (int n)
        safeStrCpy(chatPartner[activePartner], partner, MSG_SIZ);
        SetWidgetText(&chatOptions[CHAT_OUT], "", -1); // clear text if we alter partner
        SetWidgetText(&chatOptions[CHAT_IN], "", ChatDlg); // clear text if we alter partner
        safeStrCpy(chatPartner[activePartner], partner, MSG_SIZ);
        SetWidgetText(&chatOptions[CHAT_OUT], "", -1); // clear text if we alter partner
        SetWidgetText(&chatOptions[CHAT_IN], "", ChatDlg); // clear text if we alter partner
-       SetWidgetLabel(&chatOptions[activePartner+1], chatPartner[activePartner] ? chatPartner[activePartner] : _("New Chat"));
+       SetWidgetLabel(&chatOptions[activePartner+1], chatPartner[activePartner][0] ? chatPartner[activePartner] : _("New Chat"));
        HardSetFocus(&chatOptions[CHAT_IN]);
     }
     if(line[0] || hidden) { // something was typed (for ICS commands we also allow empty line!)
        HardSetFocus(&chatOptions[CHAT_IN]);
     }
     if(line[0] || hidden) { // something was typed (for ICS commands we also allow empty line!)
@@ -1842,6 +1889,15 @@ PaneSwitch ()
     Show(&chatOptions[CHAT_PANE], hidden = 1); // hide
 }
 
     Show(&chatOptions[CHAT_PANE], hidden = 1); // hide
 }
 
+static void
+NewChat (char *name)
+{   // open a chat on program request. If no empty one available, use last
+    int i;
+    for(i=0; i<MAX_CHAT-1; i++) if(!chatPartner[i][0]) break;
+    safeStrCpy(chatPartner[i], name, MSG_SIZ);
+    ChatSwitch(i+1);
+}
+
 void
 ConsoleWrite(char *message, int count)
 {
 void
 ConsoleWrite(char *message, int count)
 {
index 95e2b9c..449069e 100644 (file)
--- a/dialogs.h
+++ b/dialogs.h
@@ -132,6 +132,7 @@ extern Boolean shellUp[];
 extern Option textOptions[], typeOptions[], dualOptions[], mainOptions[];
 
 
 extern Option textOptions[], typeOptions[], dualOptions[], mainOptions[];
 
 
+void GetPlacement P((DialogClass dlg, WindowPlacement *wp));
 int DialogExists P((DialogClass n));
 int GenericPopUp P((Option *option, char *title, DialogClass dlgNr, DialogClass parent, int modal, int topLevel));
 int GenericReadout P((Option *currentOption, int selected));
 int DialogExists P((DialogClass n));
 int GenericPopUp P((Option *option, char *title, DialogClass dlgNr, DialogClass parent, int modal, int topLevel));
 int GenericReadout P((Option *currentOption, int selected));
index cdd42a7..7abd7ce 100644 (file)
@@ -522,6 +522,12 @@ GetActualPlacement (GtkWidget *shell, WindowPlacement *wp)
   frameX = 3; frameY = 3; // remember to decide if windows touch
 }
 
   frameX = 3; frameY = 3; // remember to decide if windows touch
 }
 
+void
+GetPlacement (DialogClass dlg, WindowPlacement *wp)
+{ // wrapper to shield back-end from widget type
+  if(shellUp[dlg]) GetActualPlacement(shells[dlg], wp);
+}
+
 void
 GetWindowCoords ()
 { // wrapper to shield use of window handles from back-end (make addressible by number?)
 void
 GetWindowCoords ()
 { // wrapper to shield use of window handles from back-end (make addressible by number?)
index e0c3580..04d566a 100644 (file)
@@ -707,7 +707,7 @@ AddHandler (Option *opt, DialogClass dlg, int nr)
 GtkWidget *shells[NrOfDialogs];
 DialogClass parents[NrOfDialogs];
 WindowPlacement *wp[NrOfDialogs] = { // Beware! Order must correspond to DialogClass enum
 GtkWidget *shells[NrOfDialogs];
 DialogClass parents[NrOfDialogs];
 WindowPlacement *wp[NrOfDialogs] = { // Beware! Order must correspond to DialogClass enum
-    NULL, &wpComment, &wpTags, NULL, NULL, &wpConsole, &wpDualBoard, &wpMoveHistory, &wpGameList, &wpEngineOutput, &wpEvalGraph,
+    NULL, &wpComment, &wpTags, &wpTextMenu, NULL, &wpConsole, &wpDualBoard, &wpMoveHistory, &wpGameList, &wpEngineOutput, &wpEvalGraph,
     NULL, NULL, NULL, NULL, &wpMain
 };
 
     NULL, NULL, NULL, NULL, &wpMain
 };
 
@@ -1661,8 +1661,8 @@ SendTextCB (Widget w, XtPointer client_data, Atom *selection,
 void
 SendText (int n)
 {
 void
 SendText (int n)
 {
-#ifdef TODO_GTK
     char *p = (char*) textOptions[n].choice;
     char *p = (char*) textOptions[n].choice;
+#ifdef TODO_GTK
     if(strstr(p, "$name")) {
        XtGetSelectionValue(menuBarWidget,
          XA_PRIMARY, XA_STRING,
     if(strstr(p, "$name")) {
        XtGetSelectionValue(menuBarWidget,
          XA_PRIMARY, XA_STRING,
@@ -1670,8 +1670,9 @@ SendText (int n)
          (XtPointer) (intptr_t) n, /* client_data passed to PastePositionCB */
          CurrentTime
        );
          (XtPointer) (intptr_t) n, /* client_data passed to PastePositionCB */
          CurrentTime
        );
-    } else SendString(p);
+    } else
 #endif
 #endif
+    SendString(p);
 }
 
 void
 }
 
 void
index 292f7c2..a944313 100644 (file)
@@ -638,6 +638,12 @@ GetActualPlacement (Widget wg, WindowPlacement *wp)
   frameX = winAt.x; frameY = winAt.y; // remember to decide if windows touch
 }
 
   frameX = winAt.x; frameY = winAt.y; // remember to decide if windows touch
 }
 
+void
+GetPlacement (DialogClass dlg, WindowPlacement *wp)
+{ // wrapper to shield back-end from widget type
+  if(shellUp[dlg]) GetActualPlacement(shells[dlg], wp);
+}
+
 void
 GetWindowCoords ()
 { // wrapper to shield use of window handles from back-end (make addressible by number?)
 void
 GetWindowCoords ()
 { // wrapper to shield use of window handles from back-end (make addressible by number?)
index ae0a3ed..2628979 100644 (file)
@@ -119,6 +119,7 @@ Match (name);match $name;
 Tell (name);tell $name $input;
 Play (name);play $name;
 Message (name);message $name $input;
 Tell (name);tell $name $input;
 Play (name);play $name;
 Message (name);message $name $input;
+Open Chat Box (name);$chat;
 }
 ;
 ; Save user settings.
 }
 ;
 ; Save user settings.