--- klipper/CMakeLists.txt 2009/10/10 07:49:10 1033401 +++ klipper/CMakeLists.txt 2009/10/10 07:52:36 1033402 @@ -14,6 +14,7 @@ historyurlitem.cpp actionstreewidget.cpp editactiondialog.cpp + clipcommandprocess.cpp ) kde4_add_ui_files(libklipper_common_SRCS generalconfig.ui actionsconfig.ui editactiondialog.ui) --- klipper/editactiondialog.cpp 2009/10/10 07:49:10 1033401 +++ klipper/editactiondialog.cpp 2009/10/10 07:52:36 1033402 @@ -25,6 +25,237 @@ #include "urlgrabber.h" #include "ui_editactiondialog.h" +#include +#include + +namespace { + static QString output2text(ClipCommand::Output output) { + switch(output) { + case ClipCommand::IGNORE: + return QString(i18n("Ignore")); + case ClipCommand::REPLACE: + return QString(i18n("Replace")); + case ClipCommand::ADD: + return QString(i18n("Add")); + } + return QString(); + } + +} + +/** + * Show dropdown of editing Output part of commands + */ +class ActionOutputDelegate : public QItemDelegate { + public: + ActionOutputDelegate(QObject* parent = 0) : QItemDelegate(parent){ + } + + virtual QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& /*option*/, const QModelIndex& /*index*/) const { + QComboBox* editor = new QComboBox(parent); + editor->setInsertPolicy(QComboBox::NoInsert); + editor->addItem(output2text(ClipCommand::IGNORE), QVariant::fromValue(ClipCommand::IGNORE)); + editor->addItem(output2text(ClipCommand::REPLACE), QVariant::fromValue(ClipCommand::REPLACE)); + editor->addItem(output2text(ClipCommand::ADD), QVariant::fromValue(ClipCommand::ADD)); + return editor; + + } + + virtual void setEditorData(QWidget* editor, const QModelIndex& index) const { + QComboBox* ed = static_cast(editor); + QVariant data(index.model()->data(index, Qt::EditRole)); + ed->setCurrentIndex(static_cast(data.value())); + } + + virtual void setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const { + QComboBox* ed = static_cast(editor); + model->setData(index, ed->itemData(ed->currentIndex())); + } + + virtual void updateEditorGeometry(QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& /*index*/) const { + editor->setGeometry(option.rect); + } +}; + +class ActionDetailModel : public QAbstractTableModel { + public: + ActionDetailModel(ClipAction* action, QObject* parent = 0); + virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const; + virtual bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole); + virtual Qt::ItemFlags flags(const QModelIndex& index) const; + virtual int rowCount(const QModelIndex& parent = QModelIndex()) const; + virtual int columnCount(const QModelIndex& parent) const; + virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + const QList& commands() const { return m_commands; } + void addCommand(const ClipCommand& command); + void removeCommand(const QModelIndex& index); + + private: + enum column_t { + COMMAND_COL = 0, + OUTPUT_COL = 1, + DESCRIPTION_COL = 2 + }; + QList m_commands; + QVariant displayData(ClipCommand* command, column_t colunm) const; + QVariant editData(ClipCommand* command, column_t column) const; + QVariant decorationData(ClipCommand* command, column_t column) const; + void setIconForCommand(ClipCommand& cmd); +}; + +ActionDetailModel::ActionDetailModel(ClipAction* action, QObject* parent): + QAbstractTableModel(parent), + m_commands(action->commands()) +{ + +} + +Qt::ItemFlags ActionDetailModel::flags(const QModelIndex& /*index*/) const +{ + return Qt::ItemIsEditable | Qt::ItemIsEnabled | Qt::ItemIsSelectable; +} + + +void ActionDetailModel::setIconForCommand(ClipCommand& cmd) +{ + // let's try to update icon of the item according to command + QString command = cmd.command; + if ( command.contains( ' ' ) ) { + // get first word + command = command.section( ' ', 0, 0 ); + } + + QPixmap iconPix = KIconLoader::global()->loadIcon( + command, KIconLoader::Small, 0, + KIconLoader::DefaultState, + QStringList(), 0, true /* canReturnNull */ ); + + if ( !iconPix.isNull() ) { + cmd.pixmap = command; + } else { + cmd.pixmap.clear(); + } + +} + +bool ActionDetailModel::setData(const QModelIndex& index, const QVariant& value, int role) +{ + if (role == Qt::EditRole) { + ClipCommand cmd = m_commands.at(index.row()); + switch (static_cast(index.column())) { + case COMMAND_COL: + cmd.command = value.value(); + setIconForCommand(cmd); + break; + case OUTPUT_COL: + cmd.output = value.value(); + break; + case DESCRIPTION_COL: + cmd.description = value.value(); + break; + } + m_commands.replace(index.row(), cmd); + emit dataChanged(index, index); + return true; + } + return false; +} + +int ActionDetailModel::columnCount(const QModelIndex& /*parent*/) const +{ + return 3; +} + +int ActionDetailModel::rowCount(const QModelIndex&) const +{ + return m_commands.count(); +} + +QVariant ActionDetailModel::displayData(ClipCommand* command, ActionDetailModel::column_t column) const +{ + switch (column) { + case COMMAND_COL: + return command->command; + case OUTPUT_COL: + return output2text(command->output); + case DESCRIPTION_COL: + return command->description; + } + return QVariant(); +} + +QVariant ActionDetailModel::decorationData(ClipCommand* command, ActionDetailModel::column_t column) const +{ + switch (column) { + case COMMAND_COL: + return command->pixmap.isEmpty() ? KIcon( "system-run" ) : KIcon( command->pixmap ); + case OUTPUT_COL: + case DESCRIPTION_COL: + break; + } + return QVariant(); + +} + +QVariant ActionDetailModel::editData(ClipCommand* command, ActionDetailModel::column_t column) const +{ + switch (column) { + case COMMAND_COL: + return command->command; + case OUTPUT_COL: + return QVariant::fromValue(command->output); + case DESCRIPTION_COL: + return command->description; + } + return QVariant(); + +} + +QVariant ActionDetailModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { + switch(static_cast(section)) { + case COMMAND_COL: + return i18n("Command"); + case OUTPUT_COL: + return i18n("Use Output"); + case DESCRIPTION_COL: + return i18n("Description"); + } + } + return QAbstractTableModel::headerData(section, orientation, role); +} + + +QVariant ActionDetailModel::data(const QModelIndex& index, int role) const +{ + const int column = index.column(); + const int row = index.row(); + ClipCommand cmd = m_commands.at(row); + switch (role) { + case Qt::DisplayRole: + return displayData(&cmd, static_cast(column)); + case Qt::DecorationRole: + return decorationData(&cmd, static_cast(column)); + case Qt::EditRole: + return editData(&cmd, static_cast(column)); + } + return QVariant(); +} + +void ActionDetailModel::addCommand(const ClipCommand& command) { + beginInsertRows(QModelIndex(), rowCount(), rowCount()); + m_commands << command; + endInsertRows(); +} + +void ActionDetailModel::removeCommand(const QModelIndex& index) { + int row = index.row(); + beginRemoveRows(QModelIndex(), row, row); + m_commands.removeAt(row); + endRemoveRows(); + +} EditActionDialog::EditActionDialog(QWidget* parent) : KDialog(parent) @@ -39,19 +270,14 @@ m_ui->pbAddCommand->setIcon(KIcon("list-add")); m_ui->pbRemoveCommand->setIcon(KIcon("list-remove")); - m_ui->twCommandList->header()->resizeSection( 0, 170 ); - + // For some reason, the default row height is 30 pixel. Set it to the minimum sectionSize instead, + // which is the font height+struts. + m_ui->twCommandList->verticalHeader()->setDefaultSectionSize(m_ui->twCommandList->verticalHeader()->minimumSectionSize()); setMainWidget(dlgWidget); - connect(m_ui->twCommandList, SIGNAL(itemSelectionChanged()), SLOT(onSelectionChanged())); - connect(m_ui->twCommandList, SIGNAL(itemChanged(QTreeWidgetItem*, int)), - SLOT(onItemChanged(QTreeWidgetItem*, int))); - connect(m_ui->pbAddCommand, SIGNAL( clicked() ), SLOT( onAddCommand() ) ); connect(m_ui->pbRemoveCommand, SIGNAL( clicked() ), SLOT( onRemoveCommand() ) ); - // update Remove button - onSelectionChanged(); } EditActionDialog::~EditActionDialog() @@ -62,6 +288,10 @@ void EditActionDialog::setAction(ClipAction* act, int commandIdxToSelect) { m_action = act; + m_model = new ActionDetailModel(act, this); + m_ui->twCommandList->setModel(m_model); + m_ui->twCommandList->setItemDelegateForColumn(1, new ActionOutputDelegate); + connect(m_ui->twCommandList->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), SLOT(onSelectionChanged())); updateWidgets( commandIdxToSelect ); } @@ -73,25 +303,12 @@ return; } - m_ui->twCommandList->clear(); - m_ui->leRegExp->setText(m_action->regExp()); + m_ui->automatic->setChecked(m_action->automatic()); m_ui->leDescription->setText(m_action->description()); - foreach( const ClipCommand& cmd, m_action->commands() ) { - QTreeWidgetItem* item = new QTreeWidgetItem; - item->setFlags( item->flags() | Qt::ItemIsEditable ); - - item->setText( 0, cmd.command ); - QString iconName = cmd.pixmap.isEmpty() ? "system-run" : cmd.pixmap; - item->setIcon( 0, KIcon( iconName ) ); - item->setData( 0, Qt::UserRole, iconName ); // store icon name too - item->setText( 1, cmd.description ); - m_ui->twCommandList->addTopLevelItem( item ); - } - if (commandIdxToSelect != -1) { - m_ui->twCommandList->setCurrentItem( m_ui->twCommandList->topLevelItem( commandIdxToSelect ) ); + m_ui->twCommandList->setCurrentIndex( m_model->index( commandIdxToSelect ,0 ) ); } // update Remove button @@ -107,16 +324,12 @@ m_action->setRegExp( m_ui->leRegExp->text() ); m_action->setDescription( m_ui->leDescription->text() ); + m_action->setAutomatic( m_ui->automatic->isChecked() ); m_action->clearCommands(); - int cmdCount = m_ui->twCommandList->topLevelItemCount(); - for ( int i=0; itwCommandList->topLevelItem( i ); - // we store icon name in Qt::UserRole in first column - // (see onItemChanged()) - QString iconName = item->data( 0, Qt::UserRole ).toString(); - m_action->addCommand( item->text( 0 ), item->text( 1 ), true, iconName ); + foreach ( const ClipCommand& cmd, m_model->commands() ){ + m_action->addCommand( cmd ); } } @@ -131,54 +344,21 @@ void EditActionDialog::onAddCommand() { - QTreeWidgetItem *item = new QTreeWidgetItem; - item->setFlags( item->flags() | Qt::ItemIsEditable ); - item->setText( 0, i18n( "new command" ) ); - item->setIcon( 0, KIcon( "system-run" ) ); - item->setText( 1, i18n( "Command Description" ) ); - - m_ui->twCommandList->addTopLevelItem( item ); - m_ui->twCommandList->editItem( item ); + m_model->addCommand(ClipCommand(i18n( "new command" ), + i18n( "Command Description" ), + true, + "" )); + m_ui->twCommandList->edit( m_model->index( m_model->rowCount()-1, 0 )); } void EditActionDialog::onRemoveCommand() { - QTreeWidgetItem* curItem = m_ui->twCommandList->currentItem(); - delete curItem; -} - -void EditActionDialog::onItemChanged( QTreeWidgetItem* item, int column ) -{ - if ( column == 0 ) { - // let's try to update icon of the item according to command - QString command = item->text( 0 ); - if ( command.contains( ' ' ) ) - // get first word - command = command.section( ' ', 0, 0 ); - - QPixmap iconPix = KIconLoader::global()->loadIcon( - command, KIconLoader::Small, 0, - KIconLoader::DefaultState, - QStringList(), 0, true /* canReturnNull */ ); - - // block signals to prevent infinite recursion when setIcon will trigger itemChanged again - m_ui->twCommandList->blockSignals( true ); - - if ( !iconPix.isNull() ) { - item->setIcon( 0, KIcon( command ) ); - // let's save icon name in data field (if we found something that is not "system-run") - item->setData( 0, Qt::UserRole, command ); // command is actually the icon name here :) - } else { - item->setIcon( 0, KIcon( "system-run" ) ); - } - - m_ui->twCommandList->blockSignals( false ); - } + m_model->removeCommand(m_ui->twCommandList->selectionModel()->currentIndex()); } void EditActionDialog::onSelectionChanged() { - m_ui->pbRemoveCommand->setEnabled( !m_ui->twCommandList->selectedItems().isEmpty() ); + m_ui->pbRemoveCommand->setEnabled( m_ui->twCommandList->selectionModel() && m_ui->twCommandList->selectionModel()->hasSelection() ); } #include "editactiondialog.moc" --- klipper/editactiondialog.h 2009/10/10 07:49:10 1033401 +++ klipper/editactiondialog.h 2009/10/10 07:52:36 1033402 @@ -29,7 +29,7 @@ } class ClipAction; -class QTreeWidgetItem; +class ActionDetailModel; class EditActionDialog : public KDialog { @@ -47,7 +47,7 @@ void onAddCommand(); void onRemoveCommand(); void onSelectionChanged(); - void onItemChanged( QTreeWidgetItem*, int ); +// void onItemChanged( QTreeWidgetItem*, int ); private: /** @@ -68,5 +68,6 @@ Ui::EditActionDialog* m_ui; ClipAction* m_action; + ActionDetailModel* m_model; }; #endif --- klipper/editactiondialog.ui 2009/10/10 07:49:10 1033401 +++ klipper/editactiondialog.ui 2009/10/10 07:52:36 1033402 @@ -56,6 +56,29 @@ + + + + Automatic: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + automatic + + + + + + + Qt::LeftToRight + + + + + + @@ -65,23 +88,6 @@ - - - - Double-click an item to edit - - - - Command - - - - - Description - - - - @@ -164,8 +170,50 @@ + + + + Double-click an item to edit + + + true + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + false + + + Qt::NoPen + + + false + + + true + + + false + + + false + + + + + twCommandList + leRegExp + automatic + leDescription + pbRemoveCommand + pbAddCommand + --- klipper/history.cpp 2009/10/10 07:49:10 1033401 +++ klipper/history.cpp 2009/10/10 07:52:36 1033402 @@ -49,9 +49,11 @@ m_topIsUserSelected = false; - // Optimization: Compare with top item. + // OptimizationCompare with top item. If identical, the top isn't changing if ( !itemList.isEmpty() && *itemList.first() == *item ) { - delete item; + const HistoryItem* top = itemList.first(); + itemList.first() = item; + delete top; return; } @@ -85,6 +87,7 @@ if ( !newItem ) return; + // TODO: This is rather broken.. it only checks by pointer! if (itemList.contains(newItem)) { itemList.removeAll(newItem); emit changed(); --- klipper/klipper.cpp 2009/10/10 07:49:10 1033401 +++ klipper/klipper.cpp 2009/10/10 07:52:36 1033402 @@ -158,7 +158,7 @@ /* * Create URL grabber */ - m_myURLGrabber = new URLGrabber(); + m_myURLGrabber = new URLGrabber(m_history); connect( m_myURLGrabber, SIGNAL( sigPopup( QMenu * )), SLOT( showPopupMenu( QMenu * )) ); connect( m_myURLGrabber, SIGNAL( sigDisablePopup() ), @@ -545,7 +545,7 @@ { const HistoryStringItem* top = dynamic_cast( history()->first() ); if ( top ) { - m_myURLGrabber->invokeAction( top->text() ); + m_myURLGrabber->invokeAction( top ); } } @@ -618,12 +618,15 @@ return 0; } -void Klipper::applyClipChanges( const QMimeData* clipData ) +HistoryItem* Klipper::applyClipChanges( const QMimeData* clipData ) { - if ( m_locklevel ) - return; + if ( m_locklevel ) { + return 0L; + } Ignore lock( m_locklevel ); - history()->insert( HistoryItem::create( clipData ) ); + HistoryItem* item = HistoryItem::create( clipData ); + history()->insert( item ); + return item; } @@ -798,44 +801,31 @@ m_lastClipboard = data->serialNumber(); #endif + HistoryItem* item = applyClipChanges( data ); + if (changed) { +#ifdef NOISY_KLIPPER + kDebug() << "Synchronize?" << m_bSynchronize; +#endif + if ( m_bSynchronize && item ) { + setClipboard( *item, selectionMode ? Clipboard : Selection ); + } + } QString& lastURLGrabberText = selectionMode ? m_lastURLGrabberTextSelection : m_lastURLGrabberTextClipboard; - if( data->hasText() ) + if( m_bURLGrabber && item && data->hasText()) { - if ( m_bURLGrabber ) - { - QString text = data->text(); + m_myURLGrabber->checkNewData( item ); - // Make sure URLGrabber doesn't repeat all the time if klipper reads the same - // text all the time (e.g. because XFixes is not available and the application - // has broken TIMESTAMP target). Using most recent history item may not always - // work. - if ( text != lastURLGrabberText ) - { - lastURLGrabberText = text; - if ( m_myURLGrabber->checkNewData( text ) ) - { - return; // don't add into the history - } - } + // Make sure URLGrabber doesn't repeat all the time if klipper reads the same + // text all the time (e.g. because XFixes is not available and the application + // has broken TIMESTAMP target). Using most recent history item may not always + // work. + if ( item->text() != lastURLGrabberText ) + { + lastURLGrabberText = item->text(); } - else - lastURLGrabberText = QString(); - } - else + } else { lastURLGrabberText = QString(); - - if (changed) { - applyClipChanges( data ); -#ifdef NOISY_KLIPPER - kDebug() << "Synchronize?" << m_bSynchronize; -#endif - if ( m_bSynchronize ) { - const HistoryItem* topItem = history()->first(); - if ( topItem ) { - setClipboard( *topItem, selectionMode ? Clipboard : Selection ); - } - } } } --- klipper/klipper.h 2009/10/10 07:49:10 1033401 +++ klipper/klipper.h 2009/10/10 07:52:36 1033402 @@ -123,7 +123,7 @@ /** * Enter clipboard data in the history. */ - void applyClipChanges( const QMimeData* data ); + HistoryItem* applyClipChanges( const QMimeData* data ); void setClipboard( const HistoryItem& item, int mode ); bool ignoreClipboardChanges() const; --- klipper/urlgrabber.cpp 2009/10/10 07:49:10 1033401 +++ klipper/urlgrabber.cpp 2009/10/10 07:52:36 1033402 @@ -30,7 +30,6 @@ #include #include #include -#include #include #include #include @@ -41,18 +40,21 @@ #include "klippersettings.h" #include "urlgrabber.h" +#include "clipcommandprocess.h" // TODO: // - script-interface? +#include "history.h" +#include "historystringitem.h" -URLGrabber::URLGrabber() +URLGrabber::URLGrabber(History* history): + m_myCurrentAction(0L), + m_myMenu(0L), + m_myPopupKillTimer(new QTimer( this )), + m_myPopupKillTimeout(8), + m_trimmed(true), + m_history(history) { - m_myCurrentAction = 0L; - m_myMenu = 0L; - m_myPopupKillTimeout = 8; - m_trimmed = true; - - m_myPopupKillTimer = new QTimer( this ); m_myPopupKillTimer->setSingleShot( true ); connect( m_myPopupKillTimer, SIGNAL( timeout() ), SLOT( slotKillPopupMenu() )); @@ -88,13 +90,9 @@ // Called from Klipper::slotRepeatAction, i.e. by pressing Ctrl-Alt-R // shortcut. I.e. never from clipboard monitoring // -void URLGrabber::invokeAction( const QString& clip ) +void URLGrabber::invokeAction( const HistoryItem* item ) { - if ( !clip.isEmpty() ) - m_myClipData = clip; - if ( m_trimmed ) - m_myClipData = m_myClipData.trimmed(); - + m_myClipItem = item; actionMenu( false ); } @@ -153,14 +151,14 @@ ClipAction* action = new ClipAction( QString(), mimetype->comment() ); KService::List lst = KMimeTypeTrader::self()->query( mimetype->name(), "Application" ); foreach( const KService::Ptr &service, lst ) { - action->addCommand( service->exec(), service->name(), true, service->icon() ); + action->addCommand( ClipCommand( service->exec(), service->name(), true, service->icon() ) ); } if ( !lst.isEmpty() ) m_myMatches.append( action ); } } -const ActionList& URLGrabber::matchingActions( const QString& clipData ) +const ActionList& URLGrabber::matchingActions( const QString& clipData, bool automatically_invoked ) { m_myMatches.clear(); @@ -169,41 +167,37 @@ // now look for matches in custom user actions foreach (ClipAction* action, m_myActions) { - if ( action->matches( clipData ) ) + if ( action->matches( clipData ) && (action->automatic() || !automatically_invoked) ) { m_myMatches.append( action ); + } } return m_myMatches; } -bool URLGrabber::checkNewData( const QString& clipData ) +void URLGrabber::checkNewData( const HistoryItem* item ) { // kDebug() << "** checking new data: " << clipData; - m_myClipData = clipData; - if ( m_trimmed ) - m_myClipData = m_myClipData.trimmed(); - - if ( m_myActions.isEmpty() ) - return false; + m_myClipItem = item; actionMenu( true ); // also creates m_myMatches - - return !m_myMatches.isEmpty(); } -void URLGrabber::actionMenu( bool wm_class_check ) +void URLGrabber::actionMenu( bool automatically_invoked ) { - if ( m_myClipData.isEmpty() ) - return; - - ActionList matchingActionsList = matchingActions( m_myClipData ); + QString text(m_myClipItem->text()); + if (m_trimmed) { + text.trimmed(); + } + ActionList matchingActionsList = matchingActions( text, automatically_invoked ); if (!matchingActionsList.isEmpty()) { - // don't react on konqi's/netscape's urls... - if ( wm_class_check && isAvoidedWindow() ) + // don't react on blacklisted (e.g. konqi's/netscape's urls) unless the user explicitly asked for it + if ( automatically_invoked && isAvoidedWindow() ) { return; + } m_myCommandMapper.clear(); @@ -215,7 +209,7 @@ foreach (ClipAction* clipAct, matchingActionsList) { m_myMenu->addTitle(KIcon( "klipper" ), - i18n("%1 - Actions For: %2", clipAct->description(), KStringHandler::csqueeze(m_myClipData, 45))); + i18n("%1 - Actions For: %2", clipAct->description(), KStringHandler::csqueeze(text, 45))); QList cmdList = clipAct->commands(); int listSize = cmdList.count(); for (int i=0; iaddSeparator(); QAction *disableAction = new QAction(i18n("Disable This Popup"), this); @@ -297,36 +291,26 @@ ClipCommand command = action->command(cmdIdx); if ( command.isEnabled ) { - QHash map; - map.insert( 's', m_myClipData ); - - // support %u, %U (indicates url param(s)) and %f, %F (file param(s)) - map.insert( 'u', m_myClipData ); - map.insert( 'U', m_myClipData ); - map.insert( 'f', m_myClipData ); - map.insert( 'F', m_myClipData ); - - const QStringList matches = action->regExpMatches(); - // support only %0 and the first 9 matches... - const int numMatches = qMin(10, matches.count()); - for ( int i = 0; i < numMatches; ++i ) - map.insert( QChar( '0' + i ), matches.at( i ) ); - - QString cmdLine = KMacroExpander::expandMacrosShellQuote( command.command, map ); - - if ( cmdLine.isEmpty() ) - return; - - KProcess proc; - proc.setShellCommand(cmdLine.trimmed()); - if (!proc.startDetached()) - kDebug() << "Klipper: Could not start process!"; + QString text(m_myClipItem->text()); + if (m_trimmed) { + text.trimmed(); + } + ClipCommandProcess* proc = new ClipCommandProcess(*action, command, text, m_history, m_myClipItem); + if (proc->program().isEmpty()) { + delete proc; + proc = 0L; + } else { + proc->start(); + } } } void URLGrabber::editData() { + if (!m_myClipItem) { + return; + } m_myPopupKillTimer->stop(); KDialog *dlg = new KDialog( 0 ); dlg->setModal( true ); @@ -334,14 +318,16 @@ dlg->setButtons( KDialog::Ok | KDialog::Cancel ); KTextEdit *edit = new KTextEdit( dlg ); - edit->setText( m_myClipData ); + edit->setText( m_myClipItem->text() ); edit->setFocus(); edit->setMinimumSize( 300, 40 ); dlg->setMainWidget( edit ); dlg->adjustSize(); if ( dlg->exec() == KDialog::Accepted ) { - m_myClipData = edit->toPlainText(); + QString text = edit->toPlainText(); + m_history->remove( m_myClipItem ); + m_history->insert( new HistoryStringItem(text) ); QTimer::singleShot( 0, this, SLOT( slotActionMenu() ) ); } else @@ -454,10 +440,11 @@ //////// ClipCommand::ClipCommand(const QString &_command, const QString &_description, - bool _isEnabled, const QString &_icon) + bool _isEnabled, const QString &_icon, Output _output) : command(_command), description(_description), - isEnabled(_isEnabled) + isEnabled(_isEnabled), + output(_output) { if (!_icon.isEmpty()) @@ -481,14 +468,15 @@ } -ClipAction::ClipAction( const QString& regExp, const QString& description ) - : m_myRegExp( regExp ), m_myDescription( description ) +ClipAction::ClipAction( const QString& regExp, const QString& description, bool automatic ) + : m_myRegExp( regExp ), m_myDescription( description ), m_automatic(automatic) { } ClipAction::ClipAction( KSharedConfigPtr kc, const QString& group ) : m_myRegExp( kc->group(group).readEntry("Regexp") ), - m_myDescription (kc->group(group).readEntry("Description") ) + m_myDescription (kc->group(group).readEntry("Description") ), + m_automatic(kc->group(group).readEntry("Automatic", QVariant(true)).toBool() ) { KConfigGroup cg(kc, group); @@ -499,10 +487,11 @@ QString _group = group + "/Command_%1"; KConfigGroup _cg(kc, _group.arg(i)); - addCommand( _cg.readPathEntry( "Commandline", QString() ), - _cg.readEntry( "Description" ), // i18n'ed - _cg.readEntry( "Enabled" , false), - _cg.readEntry( "Icon") ); + addCommand( ClipCommand(_cg.readPathEntry( "Commandline", QString() ), + _cg.readEntry( "Description" ), // i18n'ed + _cg.readEntry( "Enabled" , false), + _cg.readEntry( "Icon"), + static_cast(_cg.readEntry( "Output", QVariant(ClipCommand::IGNORE)).toInt()))); } } @@ -513,13 +502,12 @@ } -void ClipAction::addCommand( const QString& command, - const QString& description, bool enabled, const QString& icon ) +void ClipAction::addCommand( const ClipCommand& cmd ) { - if ( command.isEmpty() ) + if ( cmd.command.isEmpty() ) return; - m_myCommands.append( ClipCommand(command, description, enabled, icon) ); + m_myCommands.append( cmd ); } void ClipAction::replaceCommand( int idx, const ClipCommand& cmd ) @@ -529,7 +517,7 @@ return; } - m_myCommands[idx] = cmd; + m_myCommands.replace(idx, cmd); } @@ -540,6 +528,7 @@ cg.writeEntry( "Description", description() ); cg.writeEntry( "Regexp", regExp() ); cg.writeEntry( "Number of commands", m_myCommands.count() ); + cg.writeEntry( "Automatic", automatic() ); int i=0; // now iterate over all commands of this action @@ -551,6 +540,7 @@ cg.writeEntry( "Description", cmd.description ); cg.writeEntry( "Enabled", cmd.isEnabled ); cg.writeEntry( "Icon", cmd.pixmap ); + cg.writeEntry( "Output", static_cast(cmd.output) ); ++i; } --- klipper/urlgrabber.h 2009/10/10 07:49:10 1033401 +++ klipper/urlgrabber.h 2009/10/10 07:52:36 1033402 @@ -22,11 +22,17 @@ #include #include +#include +#include +class History; +class HistoryItem; class QTimer; class KConfig; class KMenu; +class QMenu; +class QAction; class ClipAction; struct ClipCommand; @@ -37,7 +43,7 @@ Q_OBJECT public: - URLGrabber(); + URLGrabber(History* history); ~URLGrabber(); /** @@ -46,8 +52,8 @@ * @returns false if the string should be put into the popupmenu or not, * otherwise true. */ - bool checkNewData( const QString& clipData ); - void invokeAction( const QString& clip = QString() ); + void checkNewData( const HistoryItem* item ); + void invokeAction( const HistoryItem* item ); ActionList actionList() const { return m_myActions; } void setActionList( const ActionList& ); @@ -65,7 +71,7 @@ void setStripWhiteSpace( bool enable ) { m_trimmed = enable; } private: - const ActionList& matchingActions( const QString& ); + const ActionList& matchingActions( const QString&, bool automatically_invoked ); void execute( const ClipAction *action, int commandIdx ) const; bool isAvoidedWindow() const; void actionMenu( bool wm_class_check ); @@ -74,7 +80,7 @@ ActionList m_myActions; ActionList m_myMatches; QStringList m_myAvoidWindows; - QString m_myClipData; + const HistoryItem* m_myClipItem; ClipAction *m_myCurrentAction; // holds mappings of menu action IDs to action commands (action+cmd index in it) @@ -83,6 +89,7 @@ QTimer *m_myPopupKillTimer; int m_myPopupKillTimeout; bool m_trimmed; + History* m_history; private Q_SLOTS: void slotActionMenu() { actionMenu( true ); } @@ -90,7 +97,6 @@ void slotKillPopupMenu(); void editData(); - Q_SIGNALS: void sigPopup( QMenu * ); void sigDisablePopup(); @@ -100,15 +106,30 @@ struct ClipCommand { - ClipCommand( const QString & command, const QString & description, - bool = true, const QString & icon = QString() ); + /** + * What to do with output of command + */ + enum Output { + IGNORE, // Discard output + REPLACE, // Replace clipboard entry with output + ADD // Add output as new clipboard element + }; + + ClipCommand( const QString & command, + const QString & description, + bool enabled= true, + const QString & icon = QString(), + Output output = IGNORE); QString command; QString description; bool isEnabled; QString pixmap; + Output output; }; +Q_DECLARE_METATYPE(ClipCommand::Output) + /** * Represents one configured action. An action consists of one regular * expression, an (optional) description and a list of ClipCommands @@ -118,7 +139,8 @@ { public: explicit ClipAction( const QString& regExp = QString(), - const QString& description = QString() ); + const QString& description = QString(), + bool automagic = true); ClipAction( KSharedConfigPtr kc, const QString& ); ~ClipAction(); @@ -133,15 +155,15 @@ void setDescription( const QString& d) { m_myDescription = d; } QString description() const { return m_myDescription; } + void setAutomatic( bool automatic ) { m_automatic = automatic; } + bool automatic() const { return m_automatic; } + /** * Removes all ClipCommands associated with this ClipAction. */ void clearCommands() { m_myCommands.clear(); } - void addCommand( const QString& command, - const QString& description, - bool isEnabled = true, - const QString& icon = QString() ); + void addCommand(const ClipCommand& cmd); /** * Replaces command at index @p idx with command @p newCmd @@ -165,6 +187,7 @@ QRegExp m_myRegExp; QString m_myDescription; QList m_myCommands; + bool m_automatic; }; --- /dev/null 2009-11-06 21:46:15.231670585 +0100 +++ klipper/clipcommandprocess.cpp 2009-11-07 02:00:47.444379231 +0100 @@ -0,0 +1,82 @@ +// -*- Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 8; -*- +/* + Copyright 2009 Esben Mose Hansen + + 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. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +*/ + +#include "clipcommandprocess.h" +#include "history.h" +#include "historystringitem.h" +#include +#include "urlgrabber.h" + +ClipCommandProcess::ClipCommandProcess(const ClipAction& action, const ClipCommand& command, const QString& clip, History* history, const HistoryItem* original_item) : + KProcess(), + m_history(history), + m_historyItem(original_item), + m_newhistoryItem() +{ + QHash map; + map.insert( 's', clip ); + + // support %u, %U (indicates url param(s)) and %f, %F (file param(s)) + map.insert( 'u', clip ); + map.insert( 'U', clip ); + map.insert( 'f', clip ); + map.insert( 'F', clip ); + + const QStringList matches = action.regExpMatches(); + // support only %0 and the first 9 matches... + const int numMatches = qMin(10, matches.count()); + for ( int i = 0; i < numMatches; ++i ) { + map.insert( QChar( '0' + i ), matches.at( i ) ); + } + + setOutputChannelMode(OnlyStdoutChannel); + setShellCommand(KMacroExpander::expandMacrosShellQuote( command.command, map ).trimmed()); + + connect(this, SIGNAL(finished(int,QProcess::ExitStatus)), SLOT(slotFinished(int,QProcess::ExitStatus))); + if (command.output != ClipCommand::IGNORE) { + connect(this, SIGNAL(readyRead()), SLOT(slotStdOutputAvailable())); + } + if (command.output != ClipCommand::REPLACE) { + m_historyItem = 0L; // Don't replace + } + +} + +void ClipCommandProcess::slotFinished(int /*exitCode*/, QProcess::ExitStatus /*newState*/) +{ + if (m_history) { + // If an history item was provided, remove it so that the new item can replace it + if (m_historyItem) { + m_history->remove(m_historyItem); + } + if (!m_newhistoryItem.isEmpty()) { + m_history->insert(new HistoryStringItem(m_newhistoryItem)); + } + } + deleteLater(); +} + +void ClipCommandProcess::slotStdOutputAvailable() +{ + m_newhistoryItem.append(QString::fromLocal8Bit(this->readAllStandardOutput().data())); +} + + +#include "clipcommandprocess.moc" --- /dev/null 2009-11-06 21:46:15.231670585 +0100 +++ klipper/clipcommandprocess.h 2009-11-07 02:00:47.444379231 +0100 @@ -0,0 +1,44 @@ +// -*- Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 8; -*- +/* + Copyright 2009 Esben Mose Hansen + + 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. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +*/ + +#ifndef CLIPCOMMANDPROCESS_H +#define CLIPCOMMANDPROCESS_H + +#include + +class ClipAction; +class History; +class ClipCommand; +class HistoryItem; +class ClipCommandProcess : public KProcess +{ + Q_OBJECT +public: + ClipCommandProcess(const ClipAction& action, const ClipCommand& command, const QString& clip, History* history = 0L, const HistoryItem* original_item = 0L); +public slots: + void slotStdOutputAvailable(); + void slotFinished(int exitCode, QProcess::ExitStatus newState); +private: + History* m_history; + const HistoryItem* m_historyItem; + QString m_newhistoryItem; +}; + +#endif // CLIPCOMMANDPROCESS_H