File indexing completed on 2024-04-14 14:32:19

0001 /***************************************************************************
0002             cinputline.cpp  -  input line widget
0003   This file is a part of KMuddy distribution.
0004               -------------------
0005   begin                : So Jun 29 2002
0006   copyright            : (C) 2002 by Tomas Mecir
0007   email                : kmuddy@kmuddy.com
0008 ***************************************************************************/
0009 
0010 /***************************************************************************
0011 *                                                                         *
0012 *   This program is free software; you can redistribute it and/or modify  *
0013 *   it under the terms of the GNU General Public License as published by  *
0014 *   the Free Software Foundation; either version 2 of the License, or     *
0015 *   (at your option) any later version.                                   *
0016 *                                                                         *
0017 ***************************************************************************/
0018 
0019 #include "cinputline.h"
0020 
0021 #include "cglobalsettings.h"
0022 // cConsole and cOutput are needed for tab-expansion
0023 #include "cconsole.h"
0024 #include "coutput.h"
0025 
0026 #include <QApplication>
0027 #include <QClipboard>
0028 #include <QFontDatabase>
0029 #include <QKeyEvent>
0030 
0031 class cCompletion : public KCompletion {
0032  public:
0033   cCompletion() {
0034     setOrder (KCompletion::Weighted);
0035     setSoundsEnabled (false);
0036   }
0037 
0038   /** Overridden completion function that ensures that short strings do not get expanded at all
0039    This is because we don't store short commands, and don't want the client to auto-complete them to somethign undesirable */
0040   QString makeCompletion (const QString &string) override {
0041     if (string.length() < 5) return QString();
0042     return KCompletion::makeCompletion(string);
0043   }
0044 };
0045 
0046 cInputLine::cInputLine (int sess, QString objName, QWidget *parent)
0047 : KLineEdit(parent), cActionBase (objName, sess)
0048 {
0049   connect (this, &cInputLine::returnKeyPressed, this, &cInputLine::handleEnter);
0050 
0051   //default values for selection
0052   ss = sl = 0;
0053 
0054   //enable completion
0055   KCompletion *comp = new cCompletion();
0056   setCompletionObject(comp);
0057   setAutoDeleteCompletionObject(true);
0058 
0059   //and the context menu
0060   menuitems = false;
0061   lastid = -1;
0062   menuitems = 0;
0063 
0064   //make Tab completion work
0065   tabExpanding = false;
0066   expandPos = 0;
0067   tabListPos = 0;
0068 
0069   addGlobalEventHandler ("global-settings-changed", 50, PT_NOTHING);
0070 }
0071 
0072 cInputLine::~cInputLine()
0073 {
0074   removeGlobalEventHandler ("global-settings-changed");
0075 }
0076 
0077 void cInputLine::eventNothingHandler (QString event, int)
0078 {
0079   if (event == "global-settings-changed") {
0080     cGlobalSettings *gs = cGlobalSettings::self();
0081 
0082     setFont (gs->getFont ("input-font"));
0083     keepText (gs->getBool ("keep-text"));
0084     selectKeptText (keeptext ? gs->getBool ("select-kept") : false);
0085     setArrowsHistory (gs->getBool ("cursors-browse"));
0086     setAC (gs->getBool ("auto-completion"));
0087     setACType (gs->getInt ("auto-completion-type"));
0088     setTelnetPaste (gs->getBool ("telnet-style-paste"));
0089     QPalette p = palette ();
0090     QColor bg = gs->getColor ("color-" + QString::number (gs->getInt ("input-bg-color")));
0091     QColor fg = gs->getColor ("color-" + QString::number (gs->getInt ("input-fg-color")));
0092     p.setColor (QPalette::Active, backgroundRole(), bg);
0093     p.setColor (QPalette::Active, foregroundRole(), fg);
0094     p.setColor (QPalette::Inactive, backgroundRole(), bg);
0095     p.setColor (QPalette::Inactive, foregroundRole(), fg);
0096     setPalette (p);
0097   }
0098 }
0099 
0100 QString cInputLine::actionStringHandler (QString action, int, QString &par1,
0101 const QString &)
0102 {
0103   if (action == "set-text") {
0104     setText (par1);
0105   }
0106 
0107   return QString();
0108 }
0109 
0110 void cInputLine::initialize ()
0111 {
0112   //change colors
0113   // TODO: full color support
0114   cGlobalSettings *gs = cGlobalSettings::self();
0115   QPalette p = palette ();
0116   p.setColor (QPalette::Active, backgroundRole(), gs->getColor ("color-0"));
0117   p.setColor (QPalette::Active, foregroundRole(), gs->getColor ("color-11"));
0118   p.setColor (QPalette::Inactive, backgroundRole(), gs->getColor ("color-0"));
0119   p.setColor (QPalette::Inactive, foregroundRole(), gs->getColor ("color-11"));
0120   setPalette (p);
0121 
0122   //change font
0123   setFont (QFontDatabase::systemFont (QFontDatabase::FixedFont)); //default system fixed font
0124 
0125   //set defaults
0126   keeptext = true;
0127   selectkepttext = true;
0128   arrowshistory = false;
0129   useac = false;
0130   curactype = 0;
0131 
0132   //position in history (for up/down browsing)
0133   historypos = 0;
0134 }
0135 
0136 void cInputLine::keepText (bool value)
0137 {
0138   keeptext = value;
0139 }
0140 
0141 void cInputLine::selectKeptText (bool value)
0142 {
0143   selectkepttext = value;
0144 }
0145 
0146 void cInputLine::setArrowsHistory (bool value)
0147 {
0148   arrowshistory = value;
0149 }
0150 
0151 void cInputLine::addHistory(const QString &text)
0152 {
0153   if (text.isEmpty()) return;  // do not add empty lines
0154   // do not update if the same command is sent more than once
0155   if ((lastid >= 0) && (text == menuitem[lastid])) return;
0156   if (menuitems < CMDHISTORYSIZE)
0157     menuitems++;
0158   lastid = (lastid + 1) % CMDHISTORYSIZE;
0159   menuitem[lastid] = text;
0160 }
0161 
0162 void cInputLine::setAC (bool ac)
0163 {
0164   useac = ac;
0165   setACType (curactype);
0166 }
0167 
0168 void cInputLine::setACType (int typeofac)
0169 {
0170   curactype = typeofac;
0171   if (!useac) {
0172     setCompletionMode (KCompletion::CompletionNone);
0173     return;
0174   }
0175   KCompletion::CompletionMode comp;
0176   switch (typeofac) {
0177     case 1: comp = KCompletion::CompletionMan; break;
0178     case 2: comp = KCompletion::CompletionPopup; break;
0179     case 0:
0180     default: comp = KCompletion::CompletionAuto; break;
0181   }
0182   setCompletionMode (comp);
0183 }
0184 
0185 void cInputLine::setTelnetPaste (bool tnp)
0186 {
0187   tnpaste = tnp;
0188 }
0189 
0190 void cInputLine::handleEnter (const QString &text)
0191 {
0192   // send the command
0193   invokeEvent ("command", sess(), text);
0194 
0195   // Add to auto-complete, but only if the text is at least 5 character long
0196   KCompletion *comp = completionObject();
0197   if (text.length() > 4) comp->addItem (text);
0198 
0199   //history position is 0 again, so that we can use arrows correctly
0200   historypos = 0;
0201   //update popup menu
0202   addHistory (text);
0203   //delete text if not needed
0204   if (!keeptext) setText ("");
0205   //select the whole line if needed
0206   if (selectkepttext) selectAll ();
0207 }
0208 
0209 void cInputLine::keyPressEvent (QKeyEvent *e)
0210 {
0211   // apparently newer versions of Qt don't send this event, so we need to
0212   if ((e->key() == Qt::Key_Return) || (e->key() == Qt::Key_Enter))
0213   {
0214     returnKeyPressed (text());
0215     e->accept();
0216     return;
0217   }
0218 
0219   //looks like auto-completion widget receives this event before I do, so
0220   //I don't have to care about possible conflicts...
0221   if (arrowshistory)
0222   {
0223     if (e->type() == QEvent::KeyPress)
0224     {
0225       //if none of the following is pressed
0226       if ((e->modifiers() & (Qt::ShiftModifier | Qt::ControlModifier | Qt::AltModifier | Qt::MetaModifier)) == 0)
0227       {
0228         if (e->key() == Qt::Key_Up)
0229         //shifting UP in history!
0230         {
0231           if (historypos == 0)  // remember what we wrote, if anything
0232             addHistory (text());
0233           setText (getHistory (false));
0234           if (selectkepttext)
0235             selectAll ();
0236           e->accept ();
0237           return;
0238         }
0239         if (e->key() == Qt::Key_Down)
0240         //shifting DOWN in history!
0241         {
0242           setText (getHistory (true));
0243           if (selectkepttext)
0244             selectAll ();
0245           e->accept ();
0246           return;
0247         }
0248         //ctrl+v would somehow get by the overloaded paste function, this fixes it.
0249       } else if ((e->modifiers() & Qt::ControlModifier) && (e->key() == Qt::Key_V)) {
0250         paste (QApplication::clipboard()->text());
0251         return;
0252       }
0253     }
0254   }
0255   //call parent class' event handler to handle other keys
0256   KLineEdit::keyPressEvent (e);
0257 }
0258 
0259 QString cInputLine::getHistory (bool next)
0260 //returns old text if there's nothing to do
0261 {
0262   //there's no history - nothing to do!
0263   if (menuitems == 0)
0264   return text ();
0265 
0266   //at the bottom - no change
0267   if (next && (historypos == 0)) return text();
0268   //at the top - do nothing!
0269   if ((!next) && (historypos == menuitems)) return text();
0270   //get requested command's ID
0271   int id = lastid - historypos + CMDHISTORYSIZE;
0272   //if we want NEXT:
0273   if (next) id++;
0274   //make it fit!
0275   id %= CMDHISTORYSIZE;
0276   historypos += (next ? (-1) : 1);
0277 
0278   //OKAY now that we have the ID, we get the command:
0279   QString cmd = menuitem[id];
0280   //We may have exactly the same command in the input line.
0281   //We know that there can't be two same commands next to each other in the history.
0282   //So we just recursively call this function.
0283   return (cmd == text()) ? getHistory(next) : cmd;
0284 }
0285 
0286 void cInputLine::focusInEvent (QFocusEvent *e)
0287 {
0288   //restore selection if possible
0289   if (sl > 0)
0290   setSelection (ss, sl);
0291 
0292   KLineEdit::focusInEvent (e);
0293 }
0294 
0295 void cInputLine::focusOutEvent (QFocusEvent *e)
0296 {
0297   int sels = selectionStart();
0298   if (sels >= 0)
0299   {
0300     ss = sels;
0301     sl = selectedText().length();
0302   }
0303   else
0304   ss = sl = 0;
0305   KLineEdit::focusOutEvent (e);
0306 }
0307 
0308 void cInputLine::mouseReleaseEvent (QMouseEvent *e)
0309 {
0310   if (e->button() == Qt::MidButton)
0311   {
0312     deselect ();
0313     paste (QApplication::clipboard()->text (QClipboard::Selection));
0314   }
0315   else
0316   KLineEdit::mouseReleaseEvent (e);
0317 }
0318 
0319 void cInputLine::paste ()
0320 {
0321   paste (QApplication::clipboard()->text());
0322 }
0323 
0324 void cInputLine::paste (const QString &txt)
0325 {
0326   //This function was provided by Yui Unifex. I've modified it to be
0327   //better configurable
0328   QString t = txt;
0329   if (tnpaste)
0330   {
0331     QString line;
0332     for (int i = 0; i < t.length(); ++i) {
0333       if (t[i] == '\n') {
0334         insert (line);
0335         returnKeyPressed (text());
0336         line = "";
0337       }
0338       else
0339       line.append(t[i]);
0340     }
0341 
0342     if (line.length() > 0)
0343     insert(line);
0344   }
0345   else
0346   {
0347     //standard paste - replace end-of-lines with spaces and paste
0348     int l = t.length();
0349     for (int i = 0; i < l; ++i)
0350     if (t[i] == '\n')
0351     t[i] = ' ';
0352     insert (t);
0353   }
0354 }
0355 
0356 bool cInputLine::event (QEvent *e)
0357 {
0358   if (!e)
0359   return true;
0360   if (e->type() == QEvent::KeyPress)
0361   {
0362     QKeyEvent *ke = (QKeyEvent *) e;
0363     if ((ke->key() == Qt::Key_Tab) || (ke->key() == Qt::Key_Backtab))
0364     {
0365       handleTabExpansion ();
0366       ke->accept ();
0367       return true;  //event processed
0368     }
0369     else
0370     tabExpanding = false;
0371   }
0372   if (e->type() == QEvent::MouseButtonPress)
0373   tabExpanding = false;  //stop tab expansion if we click the mouse
0374 
0375   return KLineEdit::event (e);;
0376 }
0377 
0378 void cInputLine::handleTabExpansion ()
0379 {
0380   QString t = text();
0381 
0382   if (!tabExpanding)
0383   {
0384     int cursorPos = cursorPosition () - 1;
0385     if (cursorPos <= 0)
0386     return;  //do nothing if we're too close to the start of line
0387 
0388     //find position of the start of a word
0389     expandPos = t.lastIndexOf (' ', cursorPos) + 1;
0390     if (expandPos == cursorPos + 1)
0391     return;  //do nothing if we're at a space
0392 
0393     // find the next space - we want to remove the entire word if we are in the middle of one
0394     int wordEnd = t.indexOf (' ', cursorPos);
0395     int wordLength = ((wordEnd > 0) ? wordEnd : t.length()) - expandPos;
0396 
0397     //find prefix and get list of all matching words
0398     QString prefix = t.mid (expandPos, cursorPos - expandPos + 1);
0399     int prefixLen = prefix.length();
0400     if (prefixLen < 2) return;
0401 
0402     cOutput *output = dynamic_cast<cOutput *>(object ("output"));
0403     tabWords = output->console()->words (prefix);
0404     if (tabWords.count() == 0)
0405     return;  //do nothing if we fail to find any such word
0406 
0407     //initialize the expansion
0408     tabListPos = 0;
0409 
0410     // delete the word
0411     t.remove (expandPos, wordLength);
0412 
0413   }
0414   else
0415   {
0416     //if we have expanded something previously, we need to delete it now (including prefix)
0417     int wlen = tabWords[tabListPos].length();
0418     t.remove (expandPos, wlen);  //length of that word, not including a trailing space
0419 
0420     //update position in the list
0421     tabListPos++;
0422     if (tabListPos >= tabWords.count())
0423       tabListPos = 0;
0424   }
0425 
0426   //if we are here, we expand ...
0427   tabExpanding = true;
0428   t.insert (expandPos, tabWords[tabListPos]);
0429   setText (t);
0430   setCursorPosition (expandPos + tabWords[tabListPos].length());
0431 }
0432 
0433 #include "moc_cinputline.cpp"