cconsole.cpp 16.6 KB
Newer Older
1
/***************************************************************************
Tomas Mecir's avatar
Tomas Mecir committed
2
                          cconsole.cpp  -  main displaying widget v2
3 4
    This file is a part of KMuddy distribution.
                             -------------------
Tomas Mecir's avatar
Tomas Mecir committed
5 6 7
    begin                : So Dec 21 2017
    copyright            : (C) 2002-2017 by Tomas Mecir
    email                : mecirt@gmail.com
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#include "cconsole.h"

#include "cactionmanager.h"
#include "ctextchunk.h"

24
#include <QAbstractTextDocumentLayout>
Tomas Mecir's avatar
Tomas Mecir committed
25
#include <QApplication>
26
#include <QAction>
Tomas Mecir's avatar
Tomas Mecir committed
27
#include <QDesktopServices>
28
#include <QGraphicsItemGroup>
Tomas Mecir's avatar
Tomas Mecir committed
29
#include <QGraphicsTextItem>
Tomas Mecir's avatar
Tomas Mecir committed
30
#include <QScrollBar>
31
#include <QTextBlock>
32
#include <QTextBlockFormat>
Tomas Mecir's avatar
Tomas Mecir committed
33 34
#include <QTextCursor>
#include <QTextDocument>
35
#include <QDebug>
Tomas Mecir's avatar
Tomas Mecir committed
36 37 38

#include <KActionCollection>

39

Tomas Mecir's avatar
Tomas Mecir committed
40 41 42 43
class cTextOutputItem : public QGraphicsTextItem {
public:
  cTextOutputItem(bool sec) {
    isSecondary = sec;
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
    bgcolor = Qt::black;
  }

  void setBackgroundColor (QColor color) {
    bgcolor = color;
  }

  QPainterPath opaqueArea() const override {
    return shape();
  }

  void paint (QPainter *painter, const QStyleOptionGraphicsItem *o, QWidget *w) {
    painter->setBrush (bgcolor);
    painter->drawRect (boundingRect());
    QGraphicsTextItem::paint (painter, o, w);
  }

  virtual QRectF boundingRect() const override {
Tomas Mecir's avatar
Tomas Mecir committed
62 63 64
    QRectF rect = scene()->sceneRect();
    // add +1 to each side to eliminate the selection box, which we do not want
    return QRectF (-1, -1, rect.width() + 2, rect.height() + 2);
65 66 67
  }

  void updateSize () {
Tomas Mecir's avatar
Tomas Mecir committed
68
    QRectF rect = scene()->sceneRect();
69 70 71
    double w = rect.width();
    if (textWidth() == w) return;  // don't update the width if it already is fine - it's an expensive operation
    setTextWidth (w);
72 73 74 75 76 77 78 79 80 81 82 83
    prepareGeometryChange();
  }

protected:
  bool isSecondary;
  QColor bgcolor;
};

class cScrollTextGroup : public QGraphicsItemGroup
{
public:
  cScrollTextGroup() {
84
    _percentHeight = 25;
85
    setFlag (QGraphicsItem::ItemClipsChildrenToShape);
Tomas Mecir's avatar
Tomas Mecir committed
86 87 88 89 90 91 92 93
  }

  virtual QRectF boundingRect() const override {
    QGraphicsScene *sc = scene();
    if (sc->views().isEmpty()) return QRectF (0, 0, 0, 0);
    QGraphicsView *view = sc->views().first();
    double w = view->viewport()->width();
    double h = view->viewport()->height();
94
    h = h * _percentHeight / 100;
Tomas Mecir's avatar
Tomas Mecir committed
95 96 97 98
    return QRectF (0, 0, w, h);
  }

  void setPercentHeight (int ph) {
99
    updateSize();
100 101 102 103 104
    _percentHeight = ph;
  }

  int percentHeight () {
    return _percentHeight;
Tomas Mecir's avatar
Tomas Mecir committed
105 106 107 108 109 110 111
  }

  void updateSize () {
    prepareGeometryChange();
  }

protected:
112
  int _percentHeight;
Tomas Mecir's avatar
Tomas Mecir committed
113 114
};

Tomas Mecir's avatar
Tomas Mecir committed
115 116
class cConsole::Private {
  QGraphicsScene scene;
Tomas Mecir's avatar
Tomas Mecir committed
117
  cTextOutputItem *mainText, *scrollText;
118
  cScrollTextGroup *scrollTextGroup;
Tomas Mecir's avatar
Tomas Mecir committed
119 120
  QTextDocument *text;

121
  int historySize;
Tomas Mecir's avatar
Tomas Mecir committed
122 123 124
  QColor bgcolor;
  QFont font;
  int sess;
125
  int indentChars;
126
  double charWidth, charHeight;
Tomas Mecir's avatar
Tomas Mecir committed
127
  bool wantNewLine;
128
  bool atBottom;
Tomas Mecir's avatar
Tomas Mecir committed
129 130 131 132 133 134 135 136 137 138 139 140

  friend class cConsole;
};



cConsole::cConsole(QWidget *parent) : QGraphicsView(parent) {
  d = new Private;
  d->sess = -1;
  d->charWidth = 12;
  d->charHeight = 12;
  d->wantNewLine = false;
141
  d->atBottom = true;
142
  d->historySize = 1000;
143
  d->indentChars = 0;
Tomas Mecir's avatar
Tomas Mecir committed
144

Tomas Mecir's avatar
Tomas Mecir committed
145 146 147 148 149 150 151
  setHorizontalScrollBarPolicy (Qt::ScrollBarAlwaysOff);
  setVerticalScrollBarPolicy (Qt::ScrollBarAlwaysOn);

  //and connect() slider so that aconsole is shown/hidden as needed
  connect (verticalScrollBar (), SIGNAL (sliderMoved (int)), this, SLOT (sliderChanged (int)));
  connect (verticalScrollBar (), SIGNAL (valueChanged (int)), this, SLOT (sliderChanged (int)));
    
Tomas Mecir's avatar
Tomas Mecir committed
152
  d->text = new QTextDocument;
153 154
//  QString stylesheet = "* { white-space: pre-wrap; } a { color: " + QColor (Qt::blue).name() + "; }  ";
//  d->text->setDefaultStyleSheet (stylesheet);
Tomas Mecir's avatar
Tomas Mecir committed
155 156 157 158
  QTextOption opt;
  opt.setWrapMode (QTextOption::WrapAtWordBoundaryOrAnywhere);
  d->text->setDefaultTextOption (opt);

159 160 161
  //size policy
  QSizePolicy qsp (QSizePolicy::Expanding, QSizePolicy::Expanding);
  setSizePolicy (qsp);
Tomas Mecir's avatar
Tomas Mecir committed
162
  setFocusPolicy (Qt::NoFocus);
163 164 165 166 167 168

  // scene
  setScene (&d->scene);
  // this needs to be set immediately to prevent an endless recursion
  scene()->setSceneRect (0, 0, viewport()->width(), viewport()->height());

Tomas Mecir's avatar
Tomas Mecir committed
169 170
  d->mainText = new cTextOutputItem (false);
  d->scrollText = new cTextOutputItem (true);
171
  d->scrollTextGroup = new cScrollTextGroup;
Tomas Mecir's avatar
Tomas Mecir committed
172
  d->scene.addItem (d->mainText);
173
  d->scene.addItem (d->scrollTextGroup);
Tomas Mecir's avatar
Tomas Mecir committed
174
  d->scene.addItem (d->scrollText);
Tomas Mecir's avatar
Tomas Mecir committed
175 176 177 178
  connect (d->mainText, &QGraphicsTextItem::linkActivated, this, &cConsole::linkActivated);
  connect (d->scrollText, &QGraphicsTextItem::linkActivated, this, &cConsole::linkActivated);
  connect (d->mainText, &QGraphicsTextItem::linkHovered, this, &cConsole::linkHovered);
  connect (d->scrollText, &QGraphicsTextItem::linkHovered, this, &cConsole::linkHovered);
Tomas Mecir's avatar
Tomas Mecir committed
179

Tomas Mecir's avatar
Tomas Mecir committed
180
  d->mainText->setDocument (d->text);
181
//  d->mainText->setFiltersChildEvents (true);
Tomas Mecir's avatar
Tomas Mecir committed
182
  d->mainText->setPos (0, 0);
183 184 185 186
  d->mainText->setTextInteractionFlags (Qt::TextBrowserInteraction);

  d->scrollTextGroup->setParentItem (d->mainText);
  d->scrollTextGroup->setVisible (false);
Tomas Mecir's avatar
Tomas Mecir committed
187 188

  d->scrollText->setDocument (d->text);
189 190 191
  d->scrollText->setParentItem (d->scrollTextGroup);
  d->scrollText->setPos (0, 0);
  d->scrollText->setTextInteractionFlags (Qt::TextBrowserInteraction);
Tomas Mecir's avatar
Tomas Mecir committed
192 193

  d->scene.setFocusItem (d->mainText);
194
  connect (&d->scene, &QGraphicsScene::changed, this, &cConsole::sceneChanged);
195 196

  //background color
197
  setDefaultBkColor (Qt::black);
198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216

  //context menu
  setContextMenuPolicy (Qt::ActionsContextMenu);
  KActionCollection *acol = cActionManager::self()->getACol();
  QAction *showmenubar = acol->action ("ShowMenuBar");
  QAction *fullscreenmode = acol->action ("SetFullScreen");
  QAction *clipcopy = acol->action ("ClipboardCopy");
  QAction *pastemenu = acol->action ("PasteMenu");
  QAction *sep1 = new QAction (this);
  QAction *sep2 = new QAction (this);
  sep1->setSeparator (true);
  sep2->setSeparator (true);
  if (clipcopy) addAction (clipcopy);
  if (pastemenu) addAction (pastemenu);
  addAction (sep1);
  if (showmenubar) addAction (showmenubar);
  addAction (sep2);
  if (fullscreenmode) addAction (fullscreenmode);

Tomas Mecir's avatar
Tomas Mecir committed
217
  setFont (QFontDatabase::systemFont (QFontDatabase::FixedFont)); //default system fixed font
218
  viewport()->setCursor (Qt::IBeamCursor);
219

220
  forceBeginOfLine ();
Tomas Mecir's avatar
Tomas Mecir committed
221
  fixupOutput(true);
222 223
}

Tomas Mecir's avatar
Tomas Mecir committed
224
cConsole::~cConsole() {
225 226
  setScene (nullptr);  // needed to prevent crashes in the destructor

Tomas Mecir's avatar
Tomas Mecir committed
227 228 229 230
  delete d->scrollText;
  delete d->mainText;
  delete d->text;
  delete d;
231 232
}

Tomas Mecir's avatar
Tomas Mecir committed
233 234
void cConsole::setSession (int s) {
  d->sess = s;
235 236
}

Tomas Mecir's avatar
Tomas Mecir committed
237 238 239
void cConsole::setFont (QFont f) {
  d->font = f;
  d->text->setDefaultFont (d->font);
240

Tomas Mecir's avatar
Tomas Mecir committed
241 242
  QFontMetrics fm (f);
  d->charWidth = fm.width ("m");
243
  d->charHeight = fm.lineSpacing() + 2;
244

Tomas Mecir's avatar
Tomas Mecir committed
245
  fixupOutput(true);
246 247
}

Tomas Mecir's avatar
Tomas Mecir committed
248 249
QFont cConsole::font () {
  return d->font;
250 251
}

Tomas Mecir's avatar
Tomas Mecir committed
252
void cConsole::setDefaultBkColor (QColor color) {
253

Tomas Mecir's avatar
Tomas Mecir committed
254 255 256 257 258
  d->bgcolor = color;
  QPalette pal = palette();
  pal.setColor (backgroundRole(), d->bgcolor);
  pal.setColor (QPalette::Base, d->bgcolor);
  setPalette (pal);
259 260 261
  setBackgroundRole (QPalette::Base);
  d->mainText->setBackgroundColor (color);
  d->scrollText->setBackgroundColor (color);
Tomas Mecir's avatar
Tomas Mecir committed
262
  update();
263 264
}

Tomas Mecir's avatar
Tomas Mecir committed
265 266
QColor cConsole::defaultBkColor () {
  return d->bgcolor;
267 268
}

269 270
void cConsole::setScrollTextVisible (bool vis)
{
271
  d->scrollTextGroup->setVisible (vis);
272 273
}

Tomas Mecir's avatar
Tomas Mecir committed
274 275 276
void cConsole::sliderChanged (int val)
{
  int maxval = verticalScrollBar()->maximum ();
277
  d->atBottom = (val >= maxval);
Tomas Mecir's avatar
Tomas Mecir committed
278 279 280 281
  bool vis = (val < maxval);
  setScrollTextVisible (vis);
}

282
void cConsole::sceneChanged (const QList<QRectF> &)
283 284 285 286 287 288
{
  // move back to the bottom if we were
  if (!d->atBottom) return;
  QScrollBar *sb  = verticalScrollBar();
  sb->setValue (sb->maximum());
}
Tomas Mecir's avatar
Tomas Mecir committed
289

Tomas Mecir's avatar
Tomas Mecir committed
290
void cConsole::setScrollTextSize (int aconsize)
291
{
292
  d->scrollTextGroup->setPercentHeight (aconsize);
293
  adjustScrollBack();
294 295
}

Tomas Mecir's avatar
Tomas Mecir committed
296
void cConsole::setIndentation (int val) {
297 298

  d->indentChars = val;
299 300
}

Tomas Mecir's avatar
Tomas Mecir committed
301 302 303
void cConsole::setEnableBlinking (bool value) {
  // TODO
}  
304

Tomas Mecir's avatar
Tomas Mecir committed
305 306
int cConsole::curRows() {
  return height() / d->charHeight;
307 308
}

Tomas Mecir's avatar
Tomas Mecir committed
309 310 311
int cConsole::curCols() {
  if (d->charWidth <= 0) return 0;
  return width() / d->charWidth;
312 313
}

Tomas Mecir's avatar
Tomas Mecir committed
314 315
void cConsole::forceEmitSize () {
  emit dimensionsChanged (curCols(), curRows());
316 317
}

318
void cConsole::dumpBuffer (bool fromcurrent, QFile &file, char dumpType) {
319 320 321 322 323 324 325
  QString contents;
  // TODO: support 'fromcurrent'
  if ((dumpType == TRANSCRIPT_PLAIN) || (dumpType == TRANSCRIPT_ANSI))
    contents = d->text->toPlainText();
  else if (dumpType == TRANSCRIPT_HTML)
    contents = d->text->toHtml();
  file.write (contents.toLocal8Bit());
326 327
}

328 329
void cConsole::setHistorySize (int size) {
  d->historySize = size;
330 331
}

332
// grab all the words from the last 100 lines that meet the prefix/length criteria
Tomas Mecir's avatar
Tomas Mecir committed
333 334
QStringList cConsole::words (QString prefix, int minLength) {
  QStringList res;
335

336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355
  int lineLimit = 100;
  int lines = 0;
  QTextBlock block = d->text->lastBlock();
  while ((block != d->text->firstBlock()) && (lines < lineLimit)) {
    lines += block.lineCount();
    QString text = block.text();
    // split the text into words
    QStringList words = text.split (QRegExp ("[\\s\\.\\,\\(\\)\\[\\]\\?\\!\\:\\;\"\']"));
    //store words that meet the requirements
    for (QString word : words) {
      if (word.length() < minLength) continue;
      if ((!prefix.isEmpty()) && (!word.startsWith (prefix, Qt::CaseInsensitive))) continue;
      // case-sensitive comparison here, so we store all the used upper/lowercase variants 
      if (res.contains (word)) continue;

      res.push_back (word);
    }

    block = block.previous();
  }
356

357
  // We intentionally don't sort the list.
Tomas Mecir's avatar
Tomas Mecir committed
358
  return res;
359 360
}

Tomas Mecir's avatar
Tomas Mecir committed
361 362 363
void cConsole::clear () {
  d->text->clear();
  update();
364 365
}

Tomas Mecir's avatar
Tomas Mecir committed
366 367
void cConsole::addLine (cTextChunk *chunk) {
  addNewText (chunk, true);
368 369
}

Tomas Mecir's avatar
Tomas Mecir committed
370 371
void cConsole::addText (cTextChunk *chunk) {
  addNewText (chunk, false);
372 373
}

374 375 376 377
int cConsole::totalLines() {
  return d->text->lineCount();
}

Tomas Mecir's avatar
Tomas Mecir committed
378
void cConsole::addNewText (cTextChunk *chunk, bool endTheLine)
379
{
Tomas Mecir's avatar
Tomas Mecir committed
380 381 382 383 384 385
  QTextCursor cursor (d->text);
  cursor.movePosition (QTextCursor::End);
  if (chunk) {
    if (d->wantNewLine) {
      cursor.insertBlock ();
      d->wantNewLine = false;
386
      QTextBlockFormat bformat = cursor.blockFormat();
387 388
      bformat.setForeground (Qt::lightGray);
      bformat.setProperty (QTextFormat::FramePadding, 1);
389
      bformat.setLineHeight (2, QTextBlockFormat::LineDistanceHeight);
390 391 392
      double px = d->indentChars * d->charWidth;  // 0 if no indentation is to happen
      bformat.setLeftMargin (px);
      bformat.setTextIndent (-1 * px);
393
      cursor.setBlockFormat (bformat);
394 395
    }

396 397
    chunk->insertToDocument (cursor);
//    cursor.insertHtml (chunk->toHTML());
398
  }
Tomas Mecir's avatar
Tomas Mecir committed
399
  if (endTheLine) d->wantNewLine = true;
400

401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416
  while (totalLines() > d->historySize) {
    // check the height of the first block; if removing it won't put us below the history limit, remove it
    QTextBlock fblock = d->text->firstBlock();
    int flines = fblock.lineCount();
    if (totalLines() - flines < d->historySize) break;

    int fheight = d->text->documentLayout()->blockBoundingRect (fblock).height();
    cursor = QTextCursor (fblock);
    cursor.select (QTextCursor::BlockUnderCursor);
    cursor.movePosition (QTextCursor::NextCharacter, QTextCursor::KeepAnchor);  // need to move, otherwise the empty block is kept
    cursor.removeSelectedText();

    // scroll accordingly if needed
    QScrollBar *bar = verticalScrollBar();
    if (!d->atBottom) bar->setValue (bar->value() - fheight);
  }
417

Tomas Mecir's avatar
Tomas Mecir committed
418
  fixupOutput(false);
419 420
}

Tomas Mecir's avatar
Tomas Mecir committed
421 422
void cConsole::forceBeginOfLine () {
  addNewText (nullptr, true);
423 424
}

Tomas Mecir's avatar
Tomas Mecir committed
425 426
void cConsole::expireNamedLinks (const QString &name) {
  // TODO
427 428
}

Tomas Mecir's avatar
Tomas Mecir committed
429
void cConsole::addSelectionToClipboard (QClipboard::Mode clipboardMode) {
Tomas Mecir's avatar
Tomas Mecir committed
430 431 432 433
  QString selection = d->mainText->textCursor().selectedText();
  if (!selection.size()) return;
  selection = selection.replace (QChar::ParagraphSeparator, '\n');
  QApplication::clipboard()->setText (selection, clipboardMode);
434 435
}

Tomas Mecir's avatar
Tomas Mecir committed
436
void cConsole::lineUp () {
437 438 439
  QScrollBar *bar = verticalScrollBar();
  int by = bar->singleStep();
  bar->setValue (bar->value() - by);
440 441
}

Tomas Mecir's avatar
Tomas Mecir committed
442
void cConsole::lineDown () {
443 444 445
  QScrollBar *bar = verticalScrollBar();
  int by = bar->singleStep();
  bar->setValue (bar->value() + by);
446 447
}

Tomas Mecir's avatar
Tomas Mecir committed
448
void cConsole::pageUp () {
449 450 451
  QScrollBar *bar = verticalScrollBar();
  int by = bar->pageStep() * (100 - d->scrollTextGroup->percentHeight()) / 100;  // deduct backview size
  bar->setValue (bar->value() - by);
452 453
}

Tomas Mecir's avatar
Tomas Mecir committed
454
void cConsole::pageDown () {
455 456 457
  QScrollBar *bar = verticalScrollBar();
  int by = bar->pageStep() * (100 - d->scrollTextGroup->percentHeight()) / 100;  // deduct backview size
  bar->setValue (bar->value() + by);
458 459
}

Tomas Mecir's avatar
Tomas Mecir committed
460
void cConsole::resizeEvent (QResizeEvent *)
461
{
Tomas Mecir's avatar
Tomas Mecir committed
462
  fixupOutput(true);
463 464
}

465 466 467 468
// this is needed to resize the text display at startup
bool cConsole::viewportEvent(QEvent *event)
{
  if (event->type() == QEvent::Resize)
Tomas Mecir's avatar
Tomas Mecir committed
469
    fixupOutput(true);
470 471 472 473
  return QGraphicsView::viewportEvent (event);
}


474
void cConsole::adjustScrollBack ()
475 476 477 478 479 480 481 482 483 484 485 486 487 488
{
  // move the scrollback to its desired position
  int h = d->scrollTextGroup->boundingRect().height();
  QPointF scenepos = mapToScene (0, height() - h);
  // don't exceed the scene height
  double diff = scenepos.y() + h - d->scene.height();
  if (diff > 0) scenepos.setY (scenepos.y() - diff);
  // and shift the text viewer
  double ty = d->scrollText->boundingRect().height() - h;

  d->scrollTextGroup->setPos (scenepos);
  d->scrollText->setPos (0, -ty);
}

489 490 491 492 493 494 495
void cConsole::scrollContentsBy (int dx, int dy)
{
  QGraphicsView::scrollContentsBy (dx, dy);

  adjustScrollBack();
}

Tomas Mecir's avatar
Tomas Mecir committed
496
void cConsole::fixupOutput (bool sizeChanged)
497
{
498 499 500 501
  double h = max ((qreal) viewport()->height(), d->text->documentLayout()->documentSize().height());
  scene()->setSceneRect (0, 0, viewport()->width(), h);

  sceneChanged();
Tomas Mecir's avatar
Tomas Mecir committed
502 503
  d->mainText->updateSize();
  d->scrollText->updateSize();
504
  adjustScrollBack ();
505

Tomas Mecir's avatar
Tomas Mecir committed
506 507
  if (sizeChanged)
    forceEmitSize ();
508 509
}

Tomas Mecir's avatar
Tomas Mecir committed
510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541
void cConsole::linkHovered (const QString &link)
{
  viewport()->setCursor (link.isEmpty() ? Qt::IBeamCursor : Qt::PointingHandCursor);
}

void cConsole::linkActivated (const QString &link)
{
  // TODO - menus and such, need to encode them in the level text. For now we just use plain text.
  // see old code below for menu handling - except that we don't have the chunk pointer

  // get the first two words, they are iscommand/toprompt
  int pos = link.indexOf(' ');
  if (pos < 0) return;
  QString w1 = link.left(pos);
  QString ll = link.mid(pos + 1);
  pos = ll.indexOf(' ');
  QString w2 = ll.left(pos);
  QString cmd = ll.mid(pos + 1);

  if (w1 == "link") {
    // URL link
    QDesktopServices::openUrl (QUrl (cmd));
    return;
  }

  if (w2 == "prompt")
    emit promptCommand (cmd);
  else
    emit sendCommand (cmd);
}


542

543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607
/*
void cConsole::activateLink (chunkLink *link, const QPoint &point)
{
  //two modes of operation, depending on whether this is a command-link or a URL-link
  if (link->isCommand())
  {
    QString cmd = link->target();
    bool toprompt = link->toPrompt();
    bool ismenu = link->isMenu();
    if (ismenu)
    {
      //get rid of old menu, if any
      delete linkMenu;
      
      link->parseMenu();
      
      //create the menu
      menuChunk = link;
      linkMenu = new KMenu (this);
      
      //insert all the items
      list<menuItem>::const_iterator it;
      for (it = menuChunk->menu().begin(); it != menuChunk->menu().end(); ++it)
        linkMenu->addAction ((*it).caption);
      connect (linkMenu, SIGNAL (triggered (QAction *)), this, SLOT (linkMenuItemHandler (QAction *)));
      
      linkMenu->popup (point);
    }
    else
    {
      if (toprompt)
        emit promptCommand (cmd);
      else
        emit sendCommand (cmd);
    }
  }
  else
  {
    QString url = link->target();
    KToolInvocation::invokeBrowser (url);
  }
}

void cConsole::linkMenuItemHandler (QAction *item)
{
  if (!linkMenu) return;
  int idx = linkMenu->actions().indexOf (item);
  if (idx == -1) return;  // not found
  bool toprompt = menuChunk->toPrompt();
  list<menuItem>::const_iterator it;
  QString cmd;
  it = menuChunk->menu().begin();
  for (int i = 0; i < idx; i++)
    ++it;
  cmd = (*it).command;
  if (toprompt)
    emit promptCommand (cmd);
  else
    emit sendCommand (cmd);

  linkMenu = 0;
  menuChunk = 0;
}
*/