1 /*
2  *
3  *  Iter Vehemens ad Necem (IVAN)
4  *  Copyright (C) Timo Kiviluoto
5  *  Released under the GNU General
6  *  Public License
7  *
8  *  See LICENSING which should be included
9  *  along with this file for more details
10  *
11  */
12 
13 #include <cstdlib>
14 #include <iostream>
15 #include <string>
16 #include <fstream>
17 #include <sstream>
18 #include <algorithm>
19 
20 #include "bitmap.h"
21 #include "feio.h"
22 #include "error.h"
23 #include "festring.h"
24 #include "graphics.h"
25 #include "rawbit.h"
26 #include "specialkeys.h"
27 #include "whandler.h"
28 
29 #include "dbgmsgproj.h"
30 
31 /**
32  * They modify current behavior of anything that accepts such modifications,
33  * or
34  * they may override whatever is happening with something else.
35  */
36 
37 //#undef REQ
38 //#define REQ(name) \
39 //  bool b##name##Request=false; \
40 //  void specialkeys::Clear##name##Request(){b##name##Requested=false;}
41 //REQ(Filter);
42 //REQ(FocusedElementHelp);
43 //REQ(CopyToClipboard);
44 //REQ(PasteFromClipboard);
45 
FilterListQuestion(cfestring what)46 cfestring specialkeys::FilterListQuestion(cfestring what)
47 {
48   festring What=what;
49 
50   v2 pos = v2(16, 6);
51   static festring Topic = "Type this list filter:";
52 
53   int R = iosystem::StringQuestion(What, Topic, pos, WHITE, 0, 30, false /*TODO !bGameIsRunning*/, true, NULL);
54 
55   /**
56    * TODO
57    * clear the filter text using the background!
58    * needed when pressing ESC or ENTER and not changing the filter!
59    * tip: igraph::BlitBackGround(pos, v2(RES.X, 23));
60    * also, could may be copy the background from doublebuffer and just paste it back after....
61    */
62 
63   // cheap workaround to not look too bad (like input was not accepted) at least
64   //  DOUBLE_BUFFER->Fill(pos,v2(RES.X, 23),BLACK);
65   FONT->Printf(DOUBLE_BUFFER, pos, BLACK, "%s", Topic.CStr());
66   FONT->Printf(DOUBLE_BUFFER, v2(pos.X, pos.Y + 10), BLACK, "%s_", What.CStr());
67 
68   if(R == NORMAL_EXIT){ DBG1(What.CStr());
69     return What;
70   }
71 
72   if(R == ABORTED)
73     return what;
74 
75   return cfestring();
76 }
77 
IsRequestedEvent(SKEvent e)78 bool specialkeys::IsRequestedEvent(SKEvent e)
79 {
80   return (e==Request);
81 }
82 
83 std::vector<festring> afsHelpDialog;
DrawHelpDialog(bitmap * Buffer)84 void DrawHelpDialog(bitmap* Buffer) //TODO this kind'o message should be more global to be easier to be used...
85 { //TODO create a buffer to not re-draw every loop... unless want to animate it...
86   if(afsHelpDialog.size()==0)return;
87 
88   int iFontLetterWidth=8;
89   int iLH=15;
90 
91   v2 v2Border;
92   v2Border.Y=afsHelpDialog.size()*iLH+iLH*2;
93   for(int i=0;i<afsHelpDialog.size();i++){
94     int iW = afsHelpDialog[i].GetSize()*iFontLetterWidth;
95     if(v2Border.X<iW)v2Border.X=iW;
96   }
97 
98   v2Border.X+=iLH*2;
99 
100   v2 v2TL(RES.X/2-v2Border.X/2,RES.Y/2-v2Border.Y/2);
101 
102   Buffer->Fill(v2TL,v2Border,BLACK);
103   graphics::DrawRectangleOutlineAround(Buffer, v2TL, v2Border, DARK_GRAY, true);
104 
105   v2TL+=v2(iLH,iLH);
106 
107   for(int i=0;i<afsHelpDialog.size();i++)
108     FONT->Printf(Buffer, v2(v2TL.X,v2TL.Y+i*iLH), WHITE, "%s", afsHelpDialog[i].CStr());
109 }
110 
111 bool bConsumingEvent=false;
IsConsumingEvent()112 bool specialkeys::IsConsumingEvent()
113 {
114   return bConsumingEvent;
115 }
116 
117 int specialkeys::Request=-1;
ConsumeEvent(SKEvent e,festring & fsInOut)118 bool specialkeys::ConsumeEvent(SKEvent e,festring& fsInOut){DBGLN;
119   if(!IsRequestedEvent(e))
120     return false;
121 
122   bConsumingEvent=true;
123 
124   switch(e){
125     case ClearStringInput:{
126       fsInOut.Empty();
127       Request=-1;
128     };break;
129 
130     case Filter:{DBGLN;
131       globalwindowhandler::SuspendKeyTimeout();
132       fsInOut=FilterListQuestion(fsInOut);
133       globalwindowhandler::ResumeKeyTimeout();
134   //    bFilterRequest=false;
135       Request=-1;
136     };break;
137 
138     case CopyToClipboard:{DBGLN;
139       #ifdef UNIX //TODO for MACOSX can use `pbcopy` see https://github.com/Attnam/ivan/pull/458#discussion_r212823516
140         /* TODO
141          * the executable OS command may allow, thru the input text, any OS commands and that is not good.
142          * ex.: echo just needs to be broken like: echo -n ''`anyOtherOSCommand`'' |xclip -i //where the input string is "'`anyOtherOSCommand`'"
143          * so this needs to be done in some other way...
144         std::ostringstream osStkCmd;
145         // this will execute an OS command
146         osStkCmd<<"echo -n '"<<fsInOut.CStr()<<"' |xclip -i";
147         FILE* pipeFile = popen(osStkCmd.str().c_str(),"r"); //TODO exec pipelessly
148         if(pipeFile!=NULL){
149           pclose(pipeFile);
150         }else{
151           DBG2("unable to execute popen() with cmd: ",osStkCmd.str());
152         }
153         */
154       #endif
155       //TODO windows (can be hardcoded)
156     };break;
157 
158     case PasteFromClipboard:{DBGLN; //TODO iosystem::StringQuestion? globalwindowhandler::GetKey at keybuffer?
159       #ifdef UNIX //TODO works for MACOSX too?
160         std::ostringstream osStkCmd;
161         osStkCmd<<"xclip -o";
162         FILE* pipeFile = popen(osStkCmd.str().c_str(),"r");
163         if(pipeFile!=NULL){
164           static const int i=10*1024;
165           char buf[i];
166           if(fread(buf,1,i,pipeFile)>0)
167             fsInOut = buf;
168           pclose(pipeFile);
169         }else{
170           DBG2("unable to execute popen() with cmd: ",osStkCmd.str());
171         }
172       #endif
173       //TODO windows (can be hardcoded)
174     };break;
175 
176     case FocusedElementHelp:{DBGLN;
177       globalwindowhandler::SuspendKeyTimeout();
178 
179       std::stringstream ss(fsInOut.CStr());
180       std::string line;
181       std::vector<festring> afs;
182       festring fsLine;
183       while(std::getline(ss,line,'\n')){ //      if(fsInOut.Find('\n',0)!=festring::NPos){
184         afs.clear();
185         fsLine=line.c_str();
186         festring::SplitString(fsLine,afs,80,0); //TODO limit line length based on fontWidth vs screenWidth
187         for(int i=0;i<afs.size();i++){
188           afsHelpDialog.push_back(festring(afs[i]));
189         }
190       }
191 
192       graphics::BlitDBToScreen();DBGLN;
193 
194       GET_KEY(true);DBGLN;
195 
196       afsHelpDialog.clear();
197 
198       globalwindowhandler::ResumeKeyTimeout();
199 
200       Request=-1; //it is com
201     };break;
202   }
203 
204   bConsumingEvent=false;
205 
206   return true;
207 }
208 
init()209 void specialkeys::init()
210 {
211   globalwindowhandler::SetFunctionKeyHandler(specialkeys::FunctionKeyHandler);
212   globalwindowhandler::SetControlKeyHandler(specialkeys::ControlKeyHandler);
213 
214   graphics::AddDrawAboveAll(&DrawHelpDialog,90000,"HelpDialog");
215 }
216 
217 typedef std::map<SDL_Keycode,specialkeyhandler> ckhmap;
218 ckhmap CkhMap;
219 
FunctionKeyHandler(SDL_Keycode key)220 bool specialkeys::FunctionKeyHandler(SDL_Keycode key)
221 {DBGLN;
222   switch(key){ //TODO how to not use SDLK_ keys here??? shouldnt anyway????
223   case SDLK_F1:DBGLN;
224     Request=FocusedElementHelp;
225     return true;
226   default:
227     ckhmap::iterator Iterator = CkhMap.find(key);
228     if(Iterator != CkhMap.end()){
229       Iterator->second();
230       return true;
231     }
232     break;
233   }
234   return false;
235 }
236 
237 /**
238  * add Function or Ctrl+ key handler
239  */
AddCtrlOrFuncKeyHandler(SDL_Keycode key,specialkeyhandler Handler)240 void specialkeys::AddCtrlOrFuncKeyHandler(SDL_Keycode key, specialkeyhandler Handler)
241 {
242   ckhmap::iterator Iterator = CkhMap.find(key);
243   if(Iterator != CkhMap.end())
244     ABORT("control key handler already set for key %d",key);
245 
246   CkhMap.insert(std::make_pair(key,Handler));
247 }
248 
249 /**
250  * Ctrl+FunctionKey is here tho
251  */
ControlKeyHandler(SDL_Keycode key)252 bool specialkeys::ControlKeyHandler(SDL_Keycode key)
253 {
254   switch(key){ //TODO use SDLK_ keys?
255   case 'f':
256     Request=Filter;
257     return true;
258   case 'c':
259     Request=CopyToClipboard;
260     return true;
261   case 'v':
262     Request=PasteFromClipboard;
263     return true;
264   case SDLK_DELETE:
265     Request=ClearStringInput;
266     return true;
267   default:
268     ckhmap::iterator Iterator = CkhMap.find(key);
269     if(Iterator != CkhMap.end()){
270       Iterator->second();
271       return true;
272     }
273     break;
274   }
275 
276   return false;
277 }
278 
279