summaryrefslogtreecommitdiffstats
path: root/klipper
diff options
context:
space:
mode:
authortoma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da>2009-11-25 17:56:58 +0000
committertoma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da>2009-11-25 17:56:58 +0000
commit4aed2c8219774f5d797760606b8489a92ddc5163 (patch)
tree3f8c130f7d269626bf6a9447407ef6c35954426a /klipper
downloadtdebase-4aed2c8219774f5d797760606b8489a92ddc5163.tar.gz
tdebase-4aed2c8219774f5d797760606b8489a92ddc5163.zip
Copy the KDE 3.5 branch to branches/trinity for new KDE 3.5 features.
BUG:215923 git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/kdebase@1054174 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'klipper')
-rw-r--r--klipper/Makefile.am51
-rw-r--r--klipper/applet.cpp141
-rw-r--r--klipper/applet.h66
-rw-r--r--klipper/clipboardpoll.cpp336
-rw-r--r--klipper/clipboardpoll.h66
-rw-r--r--klipper/configdialog.cpp465
-rw-r--r--klipper/configdialog.h263
-rw-r--r--klipper/configure.in.in11
-rw-r--r--klipper/hi128-app-klipper.pngbin0 -> 6589 bytes
-rw-r--r--klipper/hi16-app-klipper.pngbin0 -> 967 bytes
-rw-r--r--klipper/hi22-app-klipper.pngbin0 -> 1051 bytes
-rw-r--r--klipper/hi32-app-klipper.pngbin0 -> 1673 bytes
-rw-r--r--klipper/hi48-app-klipper.pngbin0 -> 2575 bytes
-rw-r--r--klipper/hi64-app-klipper.pngbin0 -> 3224 bytes
-rw-r--r--klipper/hisc-app-klipper.svgzbin0 -> 3091 bytes
-rw-r--r--klipper/history.cpp132
-rw-r--r--klipper/history.h169
-rw-r--r--klipper/historyimageitem.cpp46
-rw-r--r--klipper/historyimageitem.h61
-rw-r--r--klipper/historyitem.cpp101
-rw-r--r--klipper/historyitem.h95
-rw-r--r--klipper/historystringitem.cpp31
-rw-r--r--klipper/historystringitem.h57
-rw-r--r--klipper/historyurlitem.cpp63
-rw-r--r--klipper/historyurlitem.h49
-rw-r--r--klipper/klipper-1-2.pl63
-rw-r--r--klipper/klipper-kde31.sh8
-rw-r--r--klipper/klipper.desktop165
-rw-r--r--klipper/klipperbindings.cpp36
-rw-r--r--klipper/klipperpopup.cpp304
-rw-r--r--klipper/klipperpopup.h127
-rw-r--r--klipper/klipperrc.desktop2066
-rw-r--r--klipper/klipperrc.upd7
-rw-r--r--klipper/klippershortcuts.upd5
-rw-r--r--klipper/main.cpp67
-rw-r--r--klipper/popupproxy.cpp163
-rw-r--r--klipper/popupproxy.h93
-rw-r--r--klipper/toplevel.cpp1174
-rw-r--r--klipper/toplevel.h221
-rw-r--r--klipper/urlgrabber.cpp497
-rw-r--r--klipper/urlgrabber.h159
-rw-r--r--klipper/version.h26
42 files changed, 7384 insertions, 0 deletions
diff --git a/klipper/Makefile.am b/klipper/Makefile.am
new file mode 100644
index 000000000..114779287
--- /dev/null
+++ b/klipper/Makefile.am
@@ -0,0 +1,51 @@
+INCLUDES= $(all_includes)
+
+bin_PROGRAMS =
+lib_LTLIBRARIES =
+noinst_LTLIBRARIES = libklipper_common.la
+kdeinit_LTLIBRARIES = klipper.la
+kde_module_LTLIBRARIES = klipper_panelapplet.la
+
+CLEANFILES = dummy.cpp
+
+libklipper_common_la_LIBADD = $(LIB_KDEUI) $(LIB_KSYCOCA) $(LIB_XFIXES)
+libklipper_common_la_SOURCES = main.cpp toplevel.cpp urlgrabber.cpp configdialog.cpp \
+ toplevel.skel clipboardpoll.cpp history.cpp historyitem.cpp historystringitem.cpp klipperpopup.cpp popupproxy.cpp \
+ historyimageitem.cpp historyurlitem.cpp
+
+libklipper_common_la_METASOURCES = AUTO
+libklipper_common_la_LDFLAGS = $(all_libraries)
+
+klipper_la_LIBADD = libklipper_common.la
+klipper_la_SOURCES = dummy.cpp
+klipper_la_LDFLAGS = $(all_libraries) -module -avoid-version
+
+klipper_panelapplet_la_LIBADD = libklipper_common.la
+klipper_panelapplet_la_SOURCES = applet.cpp applet.skel
+klipper_panelapplet_la_LDFLAGS = $(all_libraries) -module -avoid-version
+
+xdg_apps_DATA = klipper.desktop
+
+autostartdir = $(datadir)/autostart
+autostart_DATA = klipper.desktop
+
+appldir = $(kde_datadir)/kicker/applets
+appl_DATA = klipper.desktop
+
+install-data-local:
+ $(mkinstalldirs) $(DESTDIR)$(kde_confdir)
+ $(INSTALL_DATA) $(srcdir)/klipperrc.desktop $(DESTDIR)$(kde_confdir)/klipperrc
+
+KDE_ICON = klipper
+
+messages:
+ $(XGETTEXT) *.cpp -o $(podir)/klipper.pot
+
+dummy.cpp:
+ echo > dummy.cpp
+
+# update script:
+updatedir = $(kde_datadir)/kconf_update
+update_DATA = klipperrc.upd klippershortcuts.upd
+update_SCRIPTS = klipper-1-2.pl klipper-kde31.sh
+
diff --git a/klipper/applet.cpp b/klipper/applet.cpp
new file mode 100644
index 000000000..298a00b3d
--- /dev/null
+++ b/klipper/applet.cpp
@@ -0,0 +1,141 @@
+// -*- Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 8; -*-
+/* This file is part of the KDE project
+ Copyright (C) by Andrew Stanley-Jones
+ Copyright (C) 2000 by Carsten Pfeiffer <[email protected]>
+
+ 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; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+#include "applet.h"
+
+#include <kaboutapplication.h>
+#include <kglobal.h>
+#include <klocale.h>
+#include <dcopclient.h>
+#include <kconfig.h>
+
+#include "toplevel.h"
+#include "history.h"
+#include "klipperpopup.h"
+
+extern "C"
+{
+ KDE_EXPORT KPanelApplet* init(QWidget *parent, const QString& configFile)
+ {
+ KGlobal::locale()->insertCatalogue("klipper");
+ int actions = KPanelApplet::Preferences | KPanelApplet::About | KPanelApplet::Help;
+ return new KlipperApplet(configFile, KPanelApplet::Normal, actions, parent, "klipper");
+ }
+}
+
+KlipperApplet::KlipperApplet(const QString& configFile, Type t, int actions,
+ QWidget *parent, const char *name)
+ : KPanelApplet(configFile, t, actions, parent, name)
+{
+ KlipperWidget::createAboutData();
+ move( 0, 0 );
+ setBackgroundMode(QWidget::X11ParentRelative);
+ widget = new KlipperAppletWidget( this );
+ setCustomMenu(widget->history()->popup());
+ centerWidget();
+ widget->show();
+}
+
+KlipperApplet::~KlipperApplet()
+{
+ widget->saveSession();
+ delete widget;
+ KlipperWidget::destroyAboutData();
+}
+
+int KlipperApplet::widthForHeight(int) const
+{
+ return widget->width();
+}
+
+int KlipperApplet::heightForWidth(int) const
+{
+ return widget->height();
+}
+
+void KlipperApplet::resizeEvent( QResizeEvent* ev )
+{
+ widget->adjustSize();
+ KPanelApplet::resizeEvent( ev );
+ centerWidget();
+}
+
+void KlipperApplet::centerWidget()
+{
+ int x = (width() - widget->width())/2;
+ int y = (height() - widget->height())/2;
+ widget->move( x, y );
+}
+
+void KlipperApplet::preferences()
+{
+ widget->slotConfigure();
+}
+
+void KlipperApplet::help()
+{
+ kapp->invokeHelp(QString::null, QString::fromLatin1("klipper"));
+}
+
+void KlipperApplet::about()
+{
+ KAboutApplication about(this, 0);
+ about.exec();
+}
+
+KlipperAppletWidget::KlipperAppletWidget( QWidget* parent )
+// init() is called first, before KlipperWidget is called with ( parent, kconfig )
+ : KlipperWidget( ( init(), parent ), new KConfig( "klipperrc" ))
+{
+}
+
+// this needs to be called before KlipperWidget ctor, because it performs already some
+// operations with the clipboard, and the other running instance could notice that
+// and request data while this instance is waiting in the DCOP call
+void KlipperAppletWidget::init()
+{
+ // if there's klipper process running, quit it
+ QByteArray arg1, arg2;
+ QCString str;
+ // call() - wait for finishing
+ kapp->dcopClient()->call("klipper", "klipper", "quitProcess()", arg1, str, arg2 );
+ // register ourselves, so if klipper process is started,
+ // it will quit immediately (KUniqueApplication)
+ s_dcop = new DCOPClient;
+ s_dcop->registerAs( "klipper", false );
+}
+
+KlipperAppletWidget::~KlipperAppletWidget()
+{
+ delete s_dcop;
+ s_dcop = 0;
+}
+
+DCOPClient* KlipperAppletWidget::s_dcop = 0;
+
+// this is just to make klipper process think we're KUniqueApplication
+// (AKA ugly hack)
+int KlipperAppletWidget::newInstance()
+{
+ return 0;
+}
+
+
+#include "applet.moc"
diff --git a/klipper/applet.h b/klipper/applet.h
new file mode 100644
index 000000000..c1a4bafcf
--- /dev/null
+++ b/klipper/applet.h
@@ -0,0 +1,66 @@
+// -*- Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 8; -*-
+/* This file is part of the KDE project
+
+ Copyright (C) Andrew Stanley-Jones
+
+ 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; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+#ifndef _APPLET_H_
+#define _APPLET_H_
+
+#include <kpanelapplet.h>
+
+#include "toplevel.h"
+
+class KlipperAppletWidget;
+
+class KlipperApplet : public KPanelApplet
+{
+ Q_OBJECT
+public:
+ KlipperApplet(const QString& configFile, Type t = Normal, int actions = 0,
+ QWidget *parent = 0, const char *name = 0);
+ ~KlipperApplet();
+
+ int widthForHeight(int h) const;
+ int heightForWidth(int w) const;
+protected:
+ void resizeEvent( QResizeEvent* );
+ void preferences();
+ void help();
+ void about();
+
+private:
+ void centerWidget();
+ KlipperAppletWidget* widget;
+};
+
+class KlipperAppletWidget : public KlipperWidget
+{
+ Q_OBJECT
+ K_DCOP
+k_dcop:
+ int newInstance();
+public:
+ KlipperAppletWidget( QWidget* parent = NULL );
+ virtual ~KlipperAppletWidget();
+private:
+ void init();
+ static DCOPClient* s_dcop;
+
+};
+
+#endif
diff --git a/klipper/clipboardpoll.cpp b/klipper/clipboardpoll.cpp
new file mode 100644
index 000000000..20157a1a2
--- /dev/null
+++ b/klipper/clipboardpoll.cpp
@@ -0,0 +1,336 @@
+// -*- Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 8; -*-
+/* This file is part of the KDE project
+
+ Copyright (C) 2003 by Lubos Lunak <[email protected]>
+
+ 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; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+#include "config.h"
+
+#include "clipboardpoll.h"
+
+#include <kapplication.h>
+#include <qclipboard.h>
+#include <kdebug.h>
+#include <X11/Xatom.h>
+#include <time.h>
+
+#ifdef HAVE_XFIXES
+#include <X11/extensions/Xfixes.h>
+#endif
+
+#include "toplevel.h"
+
+//#define NOISY_KLIPPER_
+
+/*
+
+ The polling magic:
+
+ There's no way with X11 how to find out if the selection has changed (unless its ownership
+ is taken away from the current client). In the future, there will be hopefully such notification,
+ which will make this whole file more or less obsolete. But for now, Klipper has to poll.
+ In order to avoid transferring all the data on every time pulse, this file implements two
+ optimizations: The first one is checking whether the selection owner is Qt application (using
+ the _QT_SELECTION/CLIPBOARD_SENTINEL atoms on the root window of screen 0), and if yes,
+ Klipper can rely on QClipboard's signals. If the owner is not Qt app, and the ownership has changed,
+ it means the selection has changed as well. Otherwise, first only the timestamp
+ of the last selection change is requested using the TIMESTAMP selection target, and if it's
+ the same, it's assumed the contents haven't changed. Note that some applications (like XEmacs) does
+ not provide this information, so Klipper has to assume that the clipboard might have changed in this
+ case --- this is what is meant by REFUSED below.
+
+ Update: Now there's also support for XFixes, so in case XFixes support is detected, only XFixes is
+ used for detecting changes, everything else is ignored, even Qt's clipboard signals.
+
+*/
+
+extern Time qt_x_time;
+
+ClipboardPoll::ClipboardPoll( QWidget* parent )
+ : QWidget( parent )
+ , xfixes_event_base( -1 )
+{
+ hide();
+ const char* names[ 6 ]
+ = { "_QT_SELECTION_SENTINEL",
+ "_QT_CLIPBOARD_SENTINEL",
+ "CLIPBOARD",
+ "TIMESTAMP",
+ "KLIPPER_SELECTION_TIMESTAMP",
+ "KLIPPER_CLIPBOARD_TIMESTAMP" };
+ Atom atoms[ 6 ];
+ XInternAtoms( qt_xdisplay(), const_cast< char** >( names ), 6, False, atoms );
+ selection.sentinel_atom = atoms[ 0 ];
+ clipboard.sentinel_atom = atoms[ 1 ];
+ xa_clipboard = atoms[ 2 ];
+ xa_timestamp = atoms[ 3 ];
+ selection.timestamp_atom = atoms[ 4 ];
+ clipboard.timestamp_atom = atoms[ 5 ];
+ bool use_polling = true;
+ kapp->installX11EventFilter( this );
+#ifdef HAVE_XFIXES
+ int dummy;
+ if( XFixesQueryExtension( qt_xdisplay(), &xfixes_event_base, &dummy ))
+ {
+ XFixesSelectSelectionInput( qt_xdisplay(), qt_xrootwin( 0 ), XA_PRIMARY,
+ XFixesSetSelectionOwnerNotifyMask |
+ XFixesSelectionWindowDestroyNotifyMask |
+ XFixesSelectionClientCloseNotifyMask );
+ XFixesSelectSelectionInput( qt_xdisplay(), qt_xrootwin( 0 ), xa_clipboard,
+ XFixesSetSelectionOwnerNotifyMask |
+ XFixesSelectionWindowDestroyNotifyMask |
+ XFixesSelectionClientCloseNotifyMask );
+ use_polling = false;
+#ifdef NOISY_KLIPPER_
+ kdDebug() << "Using XFIXES" << endl;
+#endif
+ }
+#endif
+ if( use_polling )
+ {
+#ifdef NOISY_KLIPPER_
+ kdDebug() << "Using polling" << endl;
+#endif
+ initPolling();
+ }
+}
+
+void ClipboardPoll::initPolling()
+{
+ connect( kapp->clipboard(), SIGNAL( selectionChanged() ), SLOT(qtSelectionChanged()));
+ connect( kapp->clipboard(), SIGNAL( dataChanged() ), SLOT( qtClipboardChanged() ));
+ connect( &timer, SIGNAL( timeout()), SLOT( timeout()));
+ timer.start( 1000, false );
+ selection.atom = XA_PRIMARY;
+ clipboard.atom = xa_clipboard;
+ selection.last_change = clipboard.last_change = qt_x_time; // don't trigger right after startup
+ selection.last_owner = XGetSelectionOwner( qt_xdisplay(), XA_PRIMARY );
+#ifdef NOISY_KLIPPER_
+ kdDebug() << "(1) Setting last_owner for =" << "selection" << ":" << selection.last_owner << endl;
+#endif
+ clipboard.last_owner = XGetSelectionOwner( qt_xdisplay(), xa_clipboard );
+#ifdef NOISY_KLIPPER_
+ kdDebug() << "(2) Setting last_owner for =" << "clipboard" << ":" << clipboard.last_owner << endl;
+#endif
+ selection.waiting_for_timestamp = false;
+ clipboard.waiting_for_timestamp = false;
+ updateQtOwnership( selection );
+ updateQtOwnership( clipboard );
+}
+
+void ClipboardPoll::qtSelectionChanged()
+{
+ emit clipboardChanged( true );
+}
+
+void ClipboardPoll::qtClipboardChanged()
+{
+ emit clipboardChanged( false );
+}
+
+bool ClipboardPoll::x11Event( XEvent* e )
+{
+// note that this is also installed as app-wide filter
+#ifdef HAVE_XFIXES
+ if( xfixes_event_base != -1 && e->type == xfixes_event_base + XFixesSelectionNotify )
+ {
+ XFixesSelectionNotifyEvent* ev = reinterpret_cast< XFixesSelectionNotifyEvent* >( e );
+ if( ev->selection == XA_PRIMARY && !kapp->clipboard()->ownsSelection())
+ {
+#ifdef NOISY_KLIPPER_
+ kdDebug() << "SELECTION CHANGED (XFIXES)" << endl;
+#endif
+ qt_x_time = ev->timestamp;
+ emit clipboardChanged( true );
+ }
+ else if( ev->selection == xa_clipboard && !kapp->clipboard()->ownsClipboard())
+ {
+#ifdef NOISY_KLIPPER_
+ kdDebug() << "CLIPBOARD CHANGED (XFIXES)" << endl;
+#endif
+ qt_x_time = ev->timestamp;
+ emit clipboardChanged( false );
+ }
+ }
+#endif
+ if( e->type == SelectionNotify && e->xselection.requestor == winId())
+ {
+ if( changedTimestamp( selection, *e ) ) {
+#ifdef NOISY_KLIPPER_
+ kdDebug() << "SELECTION CHANGED (GOT TIMESTAMP)" << endl;
+#endif
+ emit clipboardChanged( true );
+ }
+
+ if ( changedTimestamp( clipboard, *e ) )
+ {
+#ifdef NOISY_KLIPPER_
+ kdDebug() << "CLIPBOARD CHANGED (GOT TIMESTAMP)" << endl;
+#endif
+ emit clipboardChanged( false );
+ }
+ return true; // filter out
+ }
+ return false;
+}
+
+void ClipboardPoll::updateQtOwnership( SelectionData& data )
+{
+ Atom type;
+ int format;
+ unsigned long nitems;
+ unsigned long after;
+ unsigned char* prop = NULL;
+ if( XGetWindowProperty( qt_xdisplay(), qt_xrootwin( 0 ), data.sentinel_atom, 0, 2, False,
+ XA_WINDOW, &type, &format, &nitems, &after, &prop ) != Success
+ || type != XA_WINDOW || format != 32 || nitems != 2 || prop == NULL )
+ {
+#ifdef REALLY_NOISY_KLIPPER_
+ kdDebug() << "UPDATEQT BAD PROPERTY" << endl;
+#endif
+ data.owner_is_qt = false;
+ if( prop != NULL )
+ XFree( prop );
+ return;
+ }
+ Window owner = reinterpret_cast< long* >( prop )[ 0 ]; // [0] is new owner, [1] is previous
+ XFree( prop );
+ Window current_owner = XGetSelectionOwner( qt_xdisplay(), data.atom );
+ data.owner_is_qt = ( owner == current_owner );
+#ifdef REALLY_NOISY_KLIPPER_
+ kdDebug() << "owner=" << owner << "; current_owner=" << current_owner << endl;
+ kdDebug() << "UPDATEQT:" << ( &data == &selection ? "selection" : "clipboard" ) << ":" << data.owner_is_qt << endl;
+#endif
+}
+
+void ClipboardPoll::timeout()
+{
+ KlipperWidget::updateTimestamp();
+ if( !kapp->clipboard()->ownsSelection() && checkTimestamp( selection ) ) {
+#ifdef NOISY_KLIPPER_
+ kdDebug() << "SELECTION CHANGED" << endl;
+#endif
+ emit clipboardChanged( true );
+ }
+ if( !kapp->clipboard()->ownsClipboard() && checkTimestamp( clipboard ) ) {
+#ifdef NOISY_KLIPPER_
+ kdDebug() << "CLIPBOARD CHANGED" << endl;
+#endif
+ emit clipboardChanged( false );
+ }
+
+}
+
+bool ClipboardPoll::checkTimestamp( SelectionData& data )
+{
+ Window current_owner = XGetSelectionOwner( qt_xdisplay(), data.atom );
+ bool signal = false;
+ updateQtOwnership( data );
+ if( data.owner_is_qt )
+ {
+ data.last_change = CurrentTime;
+#ifdef REALLY_NOISY_KLIPPER_
+ kdDebug() << "(3) Setting last_owner for =" << ( &data==&selection ?"selection":"clipboard" ) << ":" << current_owner << endl;
+#endif
+ data.last_owner = current_owner;
+ data.waiting_for_timestamp = false;
+ return false;
+ }
+ if( current_owner != data.last_owner )
+ {
+ signal = true; // owner has changed
+ data.last_owner = current_owner;
+#ifdef REALLY_NOISY_KLIPPER_
+ kdDebug() << "(4) Setting last_owner for =" << ( &data==&selection ?"selection":"clipboard" ) << ":" << current_owner << endl;
+#endif
+ data.waiting_for_timestamp = false;
+ data.last_change = CurrentTime;
+#ifdef REALLY_NOISY_KLIPPER_
+ kdDebug() << "OWNER CHANGE:" << ( data.atom == XA_PRIMARY ) << ":" << current_owner << endl;
+#endif
+ return true;
+ }
+ if( current_owner == None ) {
+ return false; // None also last_owner...
+ }
+ if( data.waiting_for_timestamp ) {
+ // We're already waiting for the timestamp of the last check
+ return false;
+ }
+ XDeleteProperty( qt_xdisplay(), winId(), data.timestamp_atom );
+ XConvertSelection( qt_xdisplay(), data.atom, xa_timestamp, data.timestamp_atom, winId(), qt_x_time );
+ data.waiting_for_timestamp = true;
+ data.waiting_x_time = qt_x_time;
+#ifdef REALLY_NOISY_KLIPPER_
+ kdDebug() << "WAITING TIMESTAMP:" << ( data.atom == XA_PRIMARY ) << endl;
+#endif
+ return false;
+}
+
+bool ClipboardPoll::changedTimestamp( SelectionData& data, const XEvent& ev )
+{
+ if( ev.xselection.requestor != winId()
+ || ev.xselection.selection != data.atom
+ || ev.xselection.time != data.waiting_x_time )
+ {
+ return false;
+ }
+ data.waiting_for_timestamp = false;
+ if( ev.xselection.property == None )
+ {
+#ifdef NOISY_KLIPPER_
+ kdDebug() << "REFUSED:" << ( data.atom == XA_PRIMARY ) << endl;
+#endif
+ return true;
+ }
+ Atom type;
+ int format;
+ unsigned long nitems;
+ unsigned long after;
+ unsigned char* prop = NULL;
+ if( XGetWindowProperty( qt_xdisplay(), winId(), ev.xselection.property, 0, 1, False,
+ AnyPropertyType, &type, &format, &nitems, &after, &prop ) != Success
+ || format != 32 || nitems != 1 || prop == NULL )
+ {
+#ifdef NOISY_KLIPPER_
+ kdDebug() << "BAD PROPERTY:" << ( data.atom == XA_PRIMARY ) << endl;
+#endif
+ if( prop != NULL )
+ XFree( prop );
+ return true;
+ }
+ Time timestamp = reinterpret_cast< long* >( prop )[ 0 ];
+ XFree( prop );
+#ifdef NOISY_KLIPPER_
+ kdDebug() << "GOT TIMESTAMP:" << ( data.atom == XA_PRIMARY ) << endl;
+ kdDebug() << "timestamp=" << timestamp
+ << "; CurrentTime=" << CurrentTime
+ << "; last_change=" << data.last_change
+ << endl;
+#endif
+ if( timestamp != data.last_change || timestamp == CurrentTime )
+ {
+#ifdef NOISY_KLIPPER_
+ kdDebug() << "TIMESTAMP CHANGE:" << ( data.atom == XA_PRIMARY ) << endl;
+#endif
+ data.last_change = timestamp;
+ return true;
+ }
+ return false; // ok, same timestamp
+}
+
+#include "clipboardpoll.moc"
diff --git a/klipper/clipboardpoll.h b/klipper/clipboardpoll.h
new file mode 100644
index 000000000..c7b3b266d
--- /dev/null
+++ b/klipper/clipboardpoll.h
@@ -0,0 +1,66 @@
+// -*- Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 8; -*-
+/* This file is part of the KDE project
+ Copyright (C) 2003 by Lubos Lunak <[email protected]>
+
+ 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; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+#ifndef _CLIPBOARDPOLL_H_
+#define _CLIPBOARDPOLL_H_
+
+#include <qwidget.h>
+#include <qtimer.h>
+#include <X11/Xlib.h>
+#include <fixx11h.h>
+
+class ClipboardPoll
+ : public QWidget
+ {
+ Q_OBJECT
+ public:
+ ClipboardPoll( QWidget* parent );
+ signals:
+ void clipboardChanged( bool selectionMode );
+ protected:
+ virtual bool x11Event( XEvent* );
+ private slots:
+ void timeout();
+ void qtSelectionChanged();
+ void qtClipboardChanged();
+ private:
+ struct SelectionData
+ {
+ Atom atom;
+ Atom sentinel_atom;
+ Atom timestamp_atom;
+ Window last_owner;
+ bool owner_is_qt;
+ Time last_change;
+ bool waiting_for_timestamp;
+ Time waiting_x_time;
+ };
+ void updateQtOwnership( SelectionData& data );
+ bool checkTimestamp( SelectionData& data );
+ bool changedTimestamp( SelectionData& data, const XEvent& e );
+ void initPolling();
+ QTimer timer;
+ SelectionData selection;
+ SelectionData clipboard;
+ Atom xa_clipboard;
+ Atom xa_timestamp;
+ int xfixes_event_base;
+ };
+
+#endif
diff --git a/klipper/configdialog.cpp b/klipper/configdialog.cpp
new file mode 100644
index 000000000..a195a2332
--- /dev/null
+++ b/klipper/configdialog.cpp
@@ -0,0 +1,465 @@
+// -*- Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 8; -*-
+/* This file is part of the KDE project
+ Copyright (C) 2000 by Carsten Pfeiffer <[email protected]>
+
+ 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; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+#include <qlabel.h>
+#include <qlayout.h>
+#include <qlistview.h>
+#include <qpushbutton.h>
+#include <qtooltip.h>
+#include <qwhatsthis.h>
+#include <qvbuttongroup.h>
+#include <assert.h>
+
+#include <kiconloader.h>
+#include <klocale.h>
+#include <kpopupmenu.h>
+#include <kwinmodule.h>
+#include <kregexpeditorinterface.h>
+#include <kparts/componentfactory.h>
+
+#include "configdialog.h"
+
+ConfigDialog::ConfigDialog( const ActionList *list, KGlobalAccel *accel,
+ bool isApplet )
+ : KDialogBase( Tabbed, i18n("Configure"),
+ Ok | Cancel | Help,
+ Ok, 0L, "config dialog" )
+{
+ if ( isApplet )
+ setHelp( QString::null, "klipper" );
+
+ QFrame *w = 0L; // the parent for the widgets
+
+ w = addVBoxPage( i18n("&General") );
+ generalWidget = new GeneralWidget( w, "general widget" );
+
+ w = addVBoxPage( i18n("Ac&tions") );
+ actionWidget = new ActionWidget( list, this, w, "actions widget" );
+
+ w = addVBoxPage( i18n("Global &Shortcuts") );
+ keysWidget = new KKeyChooser( accel, w );
+}
+
+
+ConfigDialog::~ConfigDialog()
+{
+}
+
+// prevent huge size due to long regexps in the action-widget
+void ConfigDialog::show()
+{
+ if ( !isVisible() ) {
+ KWinModule module(0, KWinModule::INFO_DESKTOP);
+ QSize s1 = sizeHint();
+ QSize s2 = module.workArea().size();
+ int w = s1.width();
+ int h = s1.height();
+
+ if ( s1.width() >= s2.width() )
+ w = s2.width();
+ if ( s1.height() >= s2.height() )
+ h = s2.height();
+
+ resize( w, h );
+ }
+
+ KDialogBase::show();
+}
+
+void ConfigDialog::commitShortcuts()
+{
+ keysWidget->commitChanges();
+}
+
+/////////////////////////////////////////
+////
+
+
+GeneralWidget::GeneralWidget( QWidget *parent, const char *name )
+ : QVBox( parent, name )
+{
+ setSpacing(KDialog::spacingHint());
+
+ cbMousePos = new QCheckBox( i18n("&Popup menu at mouse-cursor position"),
+ this );
+ cbSaveContents = new QCheckBox( i18n("Save clipboard contents on e&xit"),
+ this );
+ cbStripWhitespace = new QCheckBox( i18n("Remove whitespace when executing actions"), this );
+ QWhatsThis::add( cbStripWhitespace,
+ i18n("Sometimes, the selected text has some whitespace at the end, which, if loaded as URL in a browser would cause an error. Enabling this option removes any whitespace at the beginning or end of the selected string (the original clipboard contents will not be modified).") );
+
+ cbReplayAIH = new QCheckBox( i18n("&Replay actions on an item selected from history"),
+ this );
+
+ cbNoNull = new QCheckBox( i18n("Pre&vent empty clipboard"), this );
+ QWhatsThis::add( cbNoNull,
+ i18n("Selecting this option has the effect, that the "
+ "clipboard can never be emptied. E.g. when an "
+ "application exits, the clipboard would usually be "
+ "emptied.") );
+
+ cbIgnoreSelection = new QCheckBox( i18n("&Ignore selection"), this );
+ QWhatsThis::add( cbIgnoreSelection,
+ i18n("This option prevents the selection being recorded "
+ "in the clipboard history. Only explicit clipboard "
+ "changes are recorded.") );
+
+ QVButtonGroup *group = new QVButtonGroup( i18n("Clipboard/Selection Behavior"), this );
+ group->setExclusive( true );
+
+ QWhatsThis::add( group,
+ i18n("<qt>There are two different clipboard buffers available:<br><br>"
+ "<b>Clipboard</b> is filled by selecting something "
+ "and pressing Ctrl+C, or by clicking \"Copy\" in a toolbar or "
+ "menubar.<br><br>"
+ "<b>Selection</b> is available immediately after "
+ "selecting some text. The only way to access the selection "
+ "is to press the middle mouse button.<br><br>"
+ "You can configure the relationship between Clipboard and Selection."
+ "</qt>" ));
+
+ cbSynchronize = new QRadioButton(
+ i18n("Sy&nchronize contents of the clipboard and the selection"),
+ group );
+ QWhatsThis::add( cbSynchronize,
+ i18n("Selecting this option synchronizes these two buffers, so they "
+ "work the same way as in KDE 1.x and 2.x.") );
+
+ cbSeparate = new QRadioButton(
+ i18n("Separate clipboard and selection"), group );
+ QWhatsThis::add(
+ cbSeparate,
+ i18n("Using this option will only set the selection when highlighting "
+ "something and the clipboard when choosing e.g. \"Copy\" "
+ "in a menubar.") );
+
+ cbSeparate->setChecked( !cbSynchronize->isChecked() );
+
+ popupTimeout = new KIntNumInput( this );
+ popupTimeout->setLabel( i18n( "Tim&eout for action popups:" ) );
+ popupTimeout->setRange( 0, 200, 1, true );
+ popupTimeout->setSuffix( i18n( " sec" ) );
+ QToolTip::add( popupTimeout, i18n("A value of 0 disables the timeout") );
+
+ maxItems = new KIntNumInput( this );
+ maxItems->setLabel( i18n( "C&lipboard history size:" ) );
+ maxItems->setRange( 2, 2048, 1, true );
+ connect( maxItems, SIGNAL( valueChanged( int )),
+ SLOT( historySizeChanged( int ) ));
+
+ connect( group, SIGNAL( clicked( int )),
+ SLOT( slotClipConfigChanged() ));
+ slotClipConfigChanged();
+
+ // Add some spacing at the end
+ QWidget *dummy = new QWidget( this );
+ setStretchFactor( dummy, 1 );
+}
+
+GeneralWidget::~GeneralWidget()
+{
+}
+
+void GeneralWidget::historySizeChanged( int value )
+{
+ // Note there is no %n in this string, because value is not supposed
+ // to be put into the suffix of the spinbox.
+ maxItems->setSuffix( i18n( " entry", " entries", value ) );
+}
+
+void GeneralWidget::slotClipConfigChanged()
+{
+ cbIgnoreSelection->setEnabled( !cbSynchronize->isChecked() );
+}
+
+/////////////////////////////////////////
+////
+
+void ListView::rename( QListViewItem* item, int c )
+{
+ bool gui = false;
+ if ( item->childCount() != 0 && c == 0) {
+ // This is the regular expression
+ if ( _configWidget->useGUIRegExpEditor() ) {
+ gui = true;
+ }
+ }
+
+ if ( gui ) {
+ if ( ! _regExpEditor )
+ _regExpEditor = KParts::ComponentFactory::createInstanceFromQuery<QDialog>( "KRegExpEditor/KRegExpEditor", QString::null, this );
+ KRegExpEditorInterface *iface = static_cast<KRegExpEditorInterface *>( _regExpEditor->qt_cast( "KRegExpEditorInterface" ) );
+ assert( iface );
+ iface->setRegExp( item->text( 0 ) );
+
+ bool ok = _regExpEditor->exec();
+ if ( ok )
+ item->setText( 0, iface->regExp() );
+
+ }
+ else
+ KListView::rename( item ,c );
+}
+
+
+ActionWidget::ActionWidget( const ActionList *list, ConfigDialog* configWidget, QWidget *parent,
+ const char *name )
+ : QVBox( parent, name ),
+ advancedWidget( 0L )
+{
+ Q_ASSERT( list != 0L );
+
+ QLabel *lblAction = new QLabel(
+ i18n("Action &list (right click to add/remove commands):"), this );
+
+ listView = new ListView( configWidget, this, "list view" );
+ lblAction->setBuddy( listView );
+ listView->addColumn( i18n("Regular Expression (see http://doc.trolltech.com/qregexp.html#details)") );
+ listView->addColumn( i18n("Description") );
+
+ listView->setRenameable(0);
+ listView->setRenameable(1);
+ listView->setItemsRenameable( true );
+ listView->setItemsMovable( false );
+// listView->setAcceptDrops( true );
+// listView->setDropVisualizer( true );
+// listView->setDragEnabled( true );
+
+ listView->setRootIsDecorated( true );
+ listView->setMultiSelection( false );
+ listView->setAllColumnsShowFocus( true );
+ listView->setSelectionMode( QListView::Single );
+ connect( listView, SIGNAL(executed( QListViewItem*, const QPoint&, int )),
+ SLOT( slotItemChanged( QListViewItem*, const QPoint& , int ) ));
+ connect( listView, SIGNAL( selectionChanged ( QListViewItem * )),
+ SLOT(selectionChanged ( QListViewItem * )));
+ connect(listView,
+ SIGNAL(contextMenu(KListView *, QListViewItem *, const QPoint&)),
+ SLOT( slotContextMenu(KListView*, QListViewItem*, const QPoint&)));
+
+ ClipAction *action = 0L;
+ ClipCommand *command = 0L;
+ QListViewItem *item = 0L;
+ QListViewItem *child = 0L;
+ QListViewItem *after = 0L; // QListView's default inserting really sucks
+ ActionListIterator it( *list );
+
+ const QPixmap& doc = SmallIcon( "misc" );
+ const QPixmap& exec = SmallIcon( "exec" );
+
+ for ( action = it.current(); action; action = ++it ) {
+ item = new QListViewItem( listView, after,
+ action->regExp(), action->description() );
+ item->setPixmap( 0, doc );
+
+ QPtrListIterator<ClipCommand> it2( action->commands() );
+ for ( command = it2.current(); command; command = ++it2 ) {
+ child = new QListViewItem( item, after,
+ command->command, command->description);
+ if ( command->pixmap.isEmpty() )
+ child->setPixmap( 0, exec );
+ else
+ child->setPixmap( 0, SmallIcon( command->pixmap ) );
+ after = child;
+ }
+ after = item;
+ }
+
+ listView->setSorting( -1 ); // newly inserted items just append unsorted
+
+ cbUseGUIRegExpEditor = new QCheckBox( i18n("&Use graphical editor for editing regular expressions" ), this );
+ if ( KTrader::self()->query("KRegExpEditor/KRegExpEditor").isEmpty() )
+ {
+ cbUseGUIRegExpEditor->hide();
+ cbUseGUIRegExpEditor->setChecked( false );
+ }
+
+ QHBox *box = new QHBox( this );
+ box->setSpacing( KDialog::spacingHint() );
+ QPushButton *button = new QPushButton( i18n("&Add Action"), box );
+ connect( button, SIGNAL( clicked() ), SLOT( slotAddAction() ));
+
+ delActionButton = new QPushButton( i18n("&Delete Action"), box );
+ connect( delActionButton, SIGNAL( clicked() ), SLOT( slotDeleteAction() ));
+
+ QLabel *label = new QLabel(i18n("Click on a highlighted item's column to change it. \"%s\" in a command will be replaced with the clipboard contents."), box);
+ label->setAlignment( WordBreak | AlignLeft | AlignVCenter );
+
+ box->setStretchFactor( label, 5 );
+
+ box = new QHBox( this );
+ QPushButton *advanced = new QPushButton( i18n("Advanced..."), box );
+ advanced->setFixedSize( advanced->sizeHint() );
+ connect( advanced, SIGNAL( clicked() ), SLOT( slotAdvanced() ));
+ (void) new QWidget( box ); // spacer
+
+ delActionButton->setEnabled(listView->currentItem () !=0);
+}
+
+ActionWidget::~ActionWidget()
+{
+}
+
+void ActionWidget::selectionChanged ( QListViewItem * item)
+{
+ delActionButton->setEnabled(item!=0);
+}
+
+void ActionWidget::slotContextMenu( KListView *, QListViewItem *item,
+ const QPoint& pos )
+{
+ if ( !item )
+ return;
+
+ int addCmd = 0, rmCmd = 0;
+ KPopupMenu *menu = new KPopupMenu;
+ addCmd = menu->insertItem( i18n("Add Command") );
+ rmCmd = menu->insertItem( i18n("Remove Command") );
+ if ( !item->parent() ) {// no "command" item
+ menu->setItemEnabled( rmCmd, false );
+ item->setOpen( true );
+ }
+
+ int id = menu->exec( pos );
+ if ( id == addCmd ) {
+ QListViewItem *p = item->parent() ? item->parent() : item;
+ QListViewItem *cmdItem = new QListViewItem( p, item,
+ i18n("Click here to set the command to be executed"),
+ i18n("<new command>") );
+ cmdItem->setPixmap( 0, SmallIcon( "exec" ) );
+ }
+ else if ( id == rmCmd )
+ delete item;
+
+ delete menu;
+}
+
+void ActionWidget::slotItemChanged( QListViewItem *item, const QPoint&, int col )
+{
+ if ( !item->parent() || col != 0 )
+ return;
+ ClipCommand command( item->text(0), item->text(1) );
+ item->setPixmap( 0, SmallIcon( command.pixmap.isEmpty() ?
+ "exec" : command.pixmap ) );
+}
+
+void ActionWidget::slotAddAction()
+{
+ QListViewItem *item = new QListViewItem( listView );
+ item->setPixmap( 0, SmallIcon( "misc" ));
+ item->setText( 0, i18n("Click here to set the regexp"));
+ item->setText( 1, i18n("<new action>"));
+}
+
+
+void ActionWidget::slotDeleteAction()
+{
+ QListViewItem *item = listView->currentItem();
+ if ( item && item->parent() )
+ item = item->parent();
+ delete item;
+}
+
+
+ActionList * ActionWidget::actionList()
+{
+ QListViewItem *item = listView->firstChild();
+ QListViewItem *child = 0L;
+ ClipAction *action = 0L;
+ ActionList *list = new ActionList;
+ list->setAutoDelete( true );
+ while ( item ) {
+ action = new ClipAction( item->text( 0 ), item->text( 1 ) );
+ child = item->firstChild();
+
+ // add the commands
+ while ( child ) {
+ action->addCommand( child->text( 0 ), child->text( 1 ), true );
+ child = child->nextSibling();
+ }
+
+ list->append( action );
+ item = item->nextSibling();
+ }
+
+ return list;
+}
+
+void ActionWidget::slotAdvanced()
+{
+ KDialogBase dlg( 0L, "advanced dlg", true,
+ i18n("Advanced Settings"),
+ KDialogBase::Ok | KDialogBase::Cancel, KDialogBase::Ok );
+ QVBox *box = dlg.makeVBoxMainWidget();
+ AdvancedWidget *widget = new AdvancedWidget( box );
+ widget->setWMClasses( m_wmClasses );
+
+ dlg.resize( dlg.sizeHint().width(),
+ dlg.sizeHint().height() +40); // or we get an ugly scrollbar :(
+
+ if ( dlg.exec() == QDialog::Accepted ) {
+ m_wmClasses = widget->wmClasses();
+ }
+}
+
+AdvancedWidget::AdvancedWidget( QWidget *parent, const char *name )
+ : QVBox( parent, name )
+{
+ editListBox = new KEditListBox( i18n("D&isable Actions for Windows of Type WM_CLASS"), this, "editlistbox", true, KEditListBox::Add | KEditListBox::Remove );
+
+ QWhatsThis::add( editListBox,
+ i18n("<qt>This lets you specify windows in which Klipper should "
+ "not invoke \"actions\". Use<br><br>"
+ "<center><b>xprop | grep WM_CLASS</b></center><br>"
+ "in a terminal to find out the WM_CLASS of a window. "
+ "Next, click on the window you want to examine. The "
+ "first string it outputs after the equal sign is the one "
+ "you need to enter here.</qt>"));
+
+ editListBox->setFocus();
+}
+
+AdvancedWidget::~AdvancedWidget()
+{
+}
+
+void AdvancedWidget::setWMClasses( const QStringList& items )
+{
+ editListBox->clear();
+ editListBox->insertStringList( items );
+}
+
+
+
+///////////////////////////////////////////////////////
+//////////
+
+/*
+KeysWidget::KeysWidget( KAccelActions &keyMap, QWidget *parent, const char *name)
+ : QVBox( parent, name )
+{
+ keyChooser = new KKeyChooser( keyMap, this );
+}
+
+KeysWidget::~KeysWidget()
+{
+}
+*/
+
+#include "configdialog.moc"
diff --git a/klipper/configdialog.h b/klipper/configdialog.h
new file mode 100644
index 000000000..be64b8335
--- /dev/null
+++ b/klipper/configdialog.h
@@ -0,0 +1,263 @@
+// -*- Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 8; -*-
+/* This file is part of the KDE project
+ Copyright (C) 2000 by Carsten Pfeiffer <[email protected]>
+
+ 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; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+#ifndef CONFIGDIALOG_H
+#define CONFIGDIALOG_H
+
+#include <qcheckbox.h>
+#include <qevent.h>
+#include <qgroupbox.h>
+#include <qheader.h>
+#include <qradiobutton.h>
+#include <qvbox.h>
+
+#include <kdialogbase.h>
+#include <keditlistbox.h>
+#include <kkeydialog.h>
+#include <klistview.h>
+#include <knuminput.h>
+
+#include "urlgrabber.h"
+
+class KGlobalAccel;
+class KKeyChooser;
+class KListView;
+class QPushButton;
+class QDialog;
+class ConfigDialog;
+
+class GeneralWidget : public QVBox
+{
+ Q_OBJECT
+
+ friend class ConfigDialog;
+
+public:
+ GeneralWidget( QWidget *parent, const char *name );
+ ~GeneralWidget();
+
+private slots:
+ void historySizeChanged( int value );
+ void slotClipConfigChanged();
+
+private:
+ QCheckBox *cbMousePos, *cbSaveContents, *cbReplayAIH, *cbNoNull;
+ QCheckBox *cbIgnoreSelection, *cbStripWhitespace;
+ QRadioButton *cbSynchronize, *cbImplicitSelection, *cbSeparate;
+ KIntNumInput *popupTimeout, *maxItems;
+
+};
+
+
+// only for use inside ActionWidget
+class AdvancedWidget : public QVBox
+{
+ Q_OBJECT
+
+public:
+ AdvancedWidget( QWidget *parent = 0L, const char *name = 0L );
+ ~AdvancedWidget();
+
+ void setWMClasses( const QStringList& items );
+ QStringList wmClasses() const { return editListBox->items(); }
+
+private:
+ KEditListBox *editListBox;
+};
+
+class ActionWidget : public QVBox
+{
+ Q_OBJECT
+
+ friend class ConfigDialog;
+
+public:
+ ActionWidget( const ActionList *list, ConfigDialog* configWidget, QWidget *parent, const char *name );
+ ~ActionWidget();
+
+ /**
+ * Creates a list of actions from the listView and returns a pointer to
+ * the list.
+ * Make sure to free that pointer when you don't need it anymore.
+ */
+ ActionList * actionList();
+
+ void setWMClasses( const QStringList& items ) { m_wmClasses = items; }
+ QStringList wmClasses() const { return m_wmClasses; }
+
+private slots:
+ void slotAddAction();
+ void slotDeleteAction();
+ void slotItemChanged( QListViewItem *, const QPoint& , int );
+ void slotAdvanced();
+ void slotContextMenu( KListView *, QListViewItem *, const QPoint& );
+ void selectionChanged ( QListViewItem *);
+
+private:
+ KListView *listView;
+ QStringList m_wmClasses;
+ AdvancedWidget *advancedWidget;
+ QPushButton *delActionButton;
+ QCheckBox *cbUseGUIRegExpEditor;
+};
+
+/*class KeysWidget : public QVBox
+{
+ Q_OBJECT
+
+ friend class ConfigDialog;
+
+public:
+ KeysWidget( KAccelActions &keyMap, QWidget *parent, const char *name );
+ ~KeysWidget();
+
+private:
+ KKeyChooser *keyChooser;
+};*/
+
+
+class ConfigDialog : public KDialogBase
+{
+ Q_OBJECT
+
+public:
+ ConfigDialog( const ActionList *list, KGlobalAccel *accel, bool isApplet );
+ ~ConfigDialog();
+
+ ActionList * actionList() const { return actionWidget->actionList(); }
+
+ bool keepContents() const {
+ return generalWidget->cbSaveContents->isChecked();
+ }
+ bool popupAtMousePos() const {
+ return generalWidget->cbMousePos->isChecked();
+ }
+ bool stripWhiteSpace() const {
+ return generalWidget->cbStripWhitespace->isChecked();
+ }
+ bool replayActionInHistory() const {
+ return generalWidget->cbReplayAIH->isChecked();
+ }
+ bool noNullClipboard() const {
+ return generalWidget->cbNoNull->isChecked();
+ }
+
+ int popupTimeout() const {
+ return generalWidget->popupTimeout->value();
+ }
+ int maxItems() const {
+ return generalWidget->maxItems->value();
+ }
+ bool ignoreSelection() const
+ {
+ return generalWidget->cbIgnoreSelection->isChecked();
+ }
+ QStringList noActionsFor() const {
+ return actionWidget->wmClasses();
+ }
+ bool useGUIRegExpEditor() const
+ {
+ return actionWidget->cbUseGUIRegExpEditor->isChecked();
+ }
+
+ bool synchronize() const {
+ return generalWidget->cbSynchronize->isChecked();
+ }
+ bool implicitSelection() const {
+ return generalWidget->cbImplicitSelection->isChecked();
+ }
+
+ void setKeepContents( bool enable ) {
+ generalWidget->cbSaveContents->setChecked( enable );
+ }
+ void setPopupAtMousePos( bool enable ) {
+ generalWidget->cbMousePos->setChecked( enable );
+ }
+ void setStripWhiteSpace( bool enable ) {
+ generalWidget->cbStripWhitespace->setChecked( enable );
+ }
+ void setReplayActionInHistory( bool enable ) {
+ generalWidget->cbReplayAIH->setChecked( enable );
+ }
+ void setNoNullClipboard( bool enable ) {
+ generalWidget->cbNoNull->setChecked( enable );
+ }
+ void setPopupTimeout( int timeout ) {
+ generalWidget->popupTimeout->setValue( timeout );
+ }
+ void setMaxItems( int items ) {
+ generalWidget->maxItems->setValue( items );
+ }
+ void setIgnoreSelection( bool ignore ) {
+ generalWidget->cbIgnoreSelection->setChecked( ignore );
+ }
+ void setSynchronize( bool synchronize ) {
+ generalWidget->cbSynchronize->setChecked( synchronize );
+ }
+ void setNoActionsFor( const QStringList& items ) {
+ actionWidget->setWMClasses( items );
+ }
+ void setUseGUIRegExpEditor( bool enabled )
+ {
+ // the checkbox is only hidden explicitly when there's no
+ // regexp editor component available.
+ if ( !actionWidget->cbUseGUIRegExpEditor->isHidden() )
+ actionWidget->cbUseGUIRegExpEditor->setChecked( enabled );
+ }
+
+ virtual void show();
+ void commitShortcuts();
+
+private:
+ GeneralWidget *generalWidget;
+ ActionWidget *actionWidget;
+ KKeyChooser *keysWidget;
+
+};
+
+class ListView : public KListView
+{
+public:
+ ListView( ConfigDialog* configWidget, QWidget *parent, const char *name )
+ : KListView( parent, name ), _configWidget( configWidget ),
+ _regExpEditor(0L) {}
+ // QListView has a weird idea of a sizeHint...
+ virtual QSize sizeHint () const {
+ int w = minimumSizeHint().width();
+ int h = header()->height();
+ h += viewport()->sizeHint().height();
+ h += horizontalScrollBar()->height();
+
+ QListViewItem *item = firstChild();
+ while ( item ) {
+ h += item->totalHeight();
+ item = item->nextSibling();
+ }
+
+ return QSize( w, h );
+ }
+
+protected:
+ virtual void rename( QListViewItem* item, int c );
+private:
+ ConfigDialog* _configWidget;
+ QDialog* _regExpEditor;
+};
+
+#endif // CONFIGDIALOG_H
diff --git a/klipper/configure.in.in b/klipper/configure.in.in
new file mode 100644
index 000000000..c958872d0
--- /dev/null
+++ b/klipper/configure.in.in
@@ -0,0 +1,11 @@
+dnl XFixes check
+KDE_CHECK_HEADER(X11/extensions/Xfixes.h, [xfixes_h=yes], [xfixes_h=no], [#include <X11/Xlib.h>])
+if test "$xfixes_h" = yes; then
+ KDE_CHECK_LIB(Xfixes, XFixesQueryExtension, [
+ LIB_XFIXES=-lXfixes
+ AC_DEFINE_UNQUOTED(HAVE_XFIXES, 1, [Define if you have the XFixes extension])
+ ], [], -lXext -lX11 $X_EXTRA_LIBS)
+else
+ LIB_XFIXES=
+fi
+AC_SUBST(LIB_XFIXES)
diff --git a/klipper/hi128-app-klipper.png b/klipper/hi128-app-klipper.png
new file mode 100644
index 000000000..bff59bcae
--- /dev/null
+++ b/klipper/hi128-app-klipper.png
Binary files differ
diff --git a/klipper/hi16-app-klipper.png b/klipper/hi16-app-klipper.png
new file mode 100644
index 000000000..2b1fbbafe
--- /dev/null
+++ b/klipper/hi16-app-klipper.png
Binary files differ
diff --git a/klipper/hi22-app-klipper.png b/klipper/hi22-app-klipper.png
new file mode 100644
index 000000000..4044ab86f
--- /dev/null
+++ b/klipper/hi22-app-klipper.png
Binary files differ
diff --git a/klipper/hi32-app-klipper.png b/klipper/hi32-app-klipper.png
new file mode 100644
index 000000000..e84fa7680
--- /dev/null
+++ b/klipper/hi32-app-klipper.png
Binary files differ
diff --git a/klipper/hi48-app-klipper.png b/klipper/hi48-app-klipper.png
new file mode 100644
index 000000000..e808a3c2d
--- /dev/null
+++ b/klipper/hi48-app-klipper.png
Binary files differ
diff --git a/klipper/hi64-app-klipper.png b/klipper/hi64-app-klipper.png
new file mode 100644
index 000000000..8d966f25d
--- /dev/null
+++ b/klipper/hi64-app-klipper.png
Binary files differ
diff --git a/klipper/hisc-app-klipper.svgz b/klipper/hisc-app-klipper.svgz
new file mode 100644
index 000000000..3f1f30d7d
--- /dev/null
+++ b/klipper/hisc-app-klipper.svgz
Binary files differ
diff --git a/klipper/history.cpp b/klipper/history.cpp
new file mode 100644
index 000000000..068475214
--- /dev/null
+++ b/klipper/history.cpp
@@ -0,0 +1,132 @@
+// -*- Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 8; -*-
+/* This file is part of the KDE project
+ Copyright (C) 2004 Esben Mose Hansen <[email protected]>
+ Copyright (C) by Andrew Stanley-Jones
+ Copyright (C) 2000 by Carsten Pfeiffer <[email protected]>
+
+ 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; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+#include <kdebug.h>
+
+#include "history.h"
+#include "historystringitem.h"
+#include "klipperpopup.h"
+
+History::History( QWidget* parent, const char* name )
+ : QObject( parent, name ),
+ m_popup( new KlipperPopup( this, parent, "main_widget" ) ),
+ m_topIsUserSelected( false )
+{
+ connect( this, SIGNAL( changed() ), m_popup, SLOT( slotHistoryChanged() ) );
+ itemList.setAutoDelete( true );
+
+}
+
+
+History::~History() {
+}
+
+History::iterator History::youngest() {
+ return iterator( itemList );
+}
+
+void History::insert( const HistoryItem* item ) {
+ if ( !item )
+ return;
+
+ m_topIsUserSelected = false;
+
+ // Optimisation: Compare with top item.
+ if ( !itemList.isEmpty() && *itemList.first() == *item ) {
+ delete item;
+ return;
+ }
+
+ remove( item );
+ forceInsert( item );
+
+ emit topChanged();
+
+}
+
+void History::forceInsert( const HistoryItem* item ) {
+ if ( !item )
+ return;
+ itemList.prepend( item );
+ emit changed();
+ trim();
+}
+
+void History::trim() {
+ int i = itemList.count() - max_size();
+ if ( i <= 0 )
+ return;
+
+ while ( i-- ) {
+ itemList.removeLast();
+ }
+ emit changed();
+}
+
+void History::remove( const HistoryItem* newItem ) {
+ if ( !newItem )
+ return;
+
+ for ( const HistoryItem* item = itemList.first(); item; item=next() ) {
+ if ( *item == *newItem ) {
+ itemList.remove();
+ emit changed();
+ return;
+ }
+ }
+
+}
+
+
+void History::slotClear() {
+ itemList.clear();
+ emit changed();
+}
+
+void History::slotMoveToTop(int pos ) {
+ if ( pos < 0 || static_cast<unsigned>( pos ) >= itemList.count() ) {
+ kdDebug() << "Argument pos out of range: " << pos << endl;
+ return;
+ }
+
+ m_topIsUserSelected = true;
+
+ itemList.first();
+ for ( ; pos; pos-- ) {
+ itemList.next();
+ }
+ HistoryItem* item = itemList.take();
+ itemList.prepend( item );
+ emit changed();
+ emit topChanged();
+}
+
+void History::max_size( unsigned max_size ) {
+ m_max_size = max_size;
+ trim();
+
+}
+
+KlipperPopup* History::popup() {
+ return m_popup;
+}
+
+#include "history.moc"
diff --git a/klipper/history.h b/klipper/history.h
new file mode 100644
index 000000000..9c54cb387
--- /dev/null
+++ b/klipper/history.h
@@ -0,0 +1,169 @@
+// -*- Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 8; -*-
+/* This file is part of the KDE project
+ Copyright (C) 2004 Esben Mose Hansen <[email protected]>
+ Copyright (C) Andrew Stanley-Jones
+
+ 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; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+#ifndef _HISTORY_H_
+#define _HISTORY_H_
+
+#include <qobject.h>
+#include <qptrlist.h>
+#include "historyitem.h"
+
+class KlipperPopup;
+class QPopupMenu;
+class QWidget;
+class QptrListIterator;
+
+class History : public QObject
+{
+ Q_OBJECT
+public:
+ History( QWidget* parent, const char* name );
+ ~History();
+ /**
+ * Iterator for history
+ */
+ typedef QPtrListIterator<HistoryItem> iterator;
+
+ /**
+ * Return (toplevel) popup menu (or default view, of you like)
+ */
+ KlipperPopup* popup();
+
+ /**
+ * Inserts item into clipboard history top
+ * if duplicate entry exist, the older duplicate is deleted.
+ * The duplicate concept is "deep", so that two text string
+ * are considerd duplicate if identical.
+ */
+ void insert( const HistoryItem* item );
+
+ /**
+ * Inserts item into clipboard without any checks
+ * Used when restoring a saved history and internally.
+ * Don't use this unless you're reasonable certain
+ * that no duplicates are introduced
+ */
+ void forceInsert( const HistoryItem* item );
+
+ /**
+ * Remove (first) history item equal to item from history
+ */
+ void remove( const HistoryItem* item );
+
+ /**
+ * Traversal: Get first item
+ */
+ const HistoryItem* first();
+
+ /**
+ * Traversal: Get current item
+ */
+ const HistoryItem* next();
+
+ /**
+ * Get an iterator pointing to the first (most recent) item
+ * This iterator should probably be a constant iterator, but
+ * the QTL doesn't support this easily.
+ *
+ * Anyway, if you modify the items via. the iterator, call changed()
+ * when you're done. Calling changed() multiple times doesn't hurt.
+ *
+ * iterator could be made into a proxy class that did the right thing.
+ */
+ iterator youngest();
+
+ /**
+ * True if no history items
+ */
+ bool empty() const { return itemList.isEmpty(); }
+
+ /**
+ * Set maximum history size
+ */
+ void max_size( unsigned max_size );
+
+ /**
+ * Get the maximum history size
+ */
+ unsigned max_size() const { return m_max_size; }
+
+ /**
+ * returns true if the user has selected the top item
+ */
+ bool topIsUserSelected() {
+ return m_topIsUserSelected;
+ }
+
+public slots:
+ /**
+ * move the history in position pos to top
+ */
+ void slotMoveToTop(int pos );
+
+ /**
+ * Clear history
+ */
+ void slotClear();
+
+signals:
+ void changed();
+
+ /**
+ * Emitted when the first history item has changed.
+ */
+ void topChanged();
+
+private:
+
+ /**
+ * The history
+ */
+ QPtrList<HistoryItem> itemList;
+
+ /**
+ * ensure that the number of items does not exceed max_size()
+ * Deletes items from the end as neccessary.
+ */
+ void trim();
+
+private:
+ /**
+ * "Default view" --- a popupmenu containing the clipboard history.
+ */
+ KlipperPopup* m_popup;
+
+
+ /**
+ * The number of clipboard items stored.
+ */
+ unsigned m_max_size;
+
+ /**
+ * True if the top is selected by the user
+ */
+ bool m_topIsUserSelected;
+
+};
+
+inline const HistoryItem* History::first() { return itemList.first(); }
+
+inline const HistoryItem* History::next() { return itemList.next(); }
+
+#endif
diff --git a/klipper/historyimageitem.cpp b/klipper/historyimageitem.cpp
new file mode 100644
index 000000000..f95a76429
--- /dev/null
+++ b/klipper/historyimageitem.cpp
@@ -0,0 +1,46 @@
+// -*- Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 8; -*-
+/* This file is part of the KDE project
+ Copyright (C) 2004 Esben Mose Hansen <[email protected]>
+
+ 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; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#include <qstring.h>
+
+#include <kdebug.h>
+
+#include "historyimageitem.h"
+
+HistoryImageItem::HistoryImageItem( const QPixmap& data )
+ : HistoryItem(), m_data( data )
+{
+}
+
+QString HistoryImageItem::text() const {
+ if ( m_text.isNull() ) {
+ m_text = QString( "%1x%2x%3 %4" )
+ .arg( m_data.width() )
+ .arg( m_data.height() )
+ .arg( m_data.depth() );
+ }
+ return m_text;
+
+}
+
+/* virtual */
+void HistoryImageItem::write( QDataStream& stream ) const {
+ stream << QString( "image" ) << m_data;
+}
diff --git a/klipper/historyimageitem.h b/klipper/historyimageitem.h
new file mode 100644
index 000000000..2518e0a4c
--- /dev/null
+++ b/klipper/historyimageitem.h
@@ -0,0 +1,61 @@
+// -*- Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 8; -*-
+/* This file is part of the KDE project
+ Copyright (C) 2004 Esben Mose Hansen <[email protected]>
+
+ 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; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#ifndef _HISTORYIMAGEITEM_H_
+#define _HISTORYIMAGEITEM_H_
+
+#include "historyitem.h"
+#include <qpixmap.h>
+#include <qdragobject.h>
+
+/**
+ * A image entry in the clipboard history.
+ */
+class HistoryImageItem : public HistoryItem
+{
+public:
+ HistoryImageItem( const QPixmap& data );
+ virtual ~HistoryImageItem() {}
+ virtual QString text() const;
+ virtual bool operator==( const HistoryItem& rhs) const {
+ if ( const HistoryImageItem* casted_rhs = dynamic_cast<const HistoryImageItem*>( &rhs ) ) {
+ return &casted_rhs->m_data == &m_data; // Not perfect, but better than nothing.
+ }
+ return false;
+ }
+ virtual const QPixmap& image() const { return m_data; }
+ virtual QMimeSource* mimeSource() const { return new QImageDrag( m_data.convertToImage()) ; }
+
+ virtual void write( QDataStream& stream ) const;
+
+private:
+ /**
+ *
+ */
+ const QPixmap m_data;
+ /**
+ * Cache for m_data's string representation
+ */
+ mutable QString m_text;
+};
+
+
+
+#endif
diff --git a/klipper/historyitem.cpp b/klipper/historyitem.cpp
new file mode 100644
index 000000000..680e6b700
--- /dev/null
+++ b/klipper/historyitem.cpp
@@ -0,0 +1,101 @@
+// -*- Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 8; -*-
+/* This file is part of the KDE project
+ Copyright (C) 2004 Esben Mose Hansen <[email protected]>
+
+ 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; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+#include <qmime.h>
+#include <qdragobject.h>
+#include <qmap.h>
+#include <qstring.h>
+#include <qpixmap.h>
+#include <kurldrag.h>
+
+#include <kdebug.h>
+
+#include "historyitem.h"
+#include "historystringitem.h"
+#include "historyimageitem.h"
+#include "historyurlitem.h"
+
+HistoryItem::HistoryItem() {
+
+}
+
+HistoryItem::~HistoryItem() {
+
+}
+
+HistoryItem* HistoryItem::create( const QMimeSource& aSource )
+{
+#if 0
+ int i=0;
+ while ( const char* f = aSource.format( i++ ) ) {
+ kdDebug() << "format(" << i <<"): " << f << endl;
+ }
+#endif
+ if( KURLDrag::canDecode( &aSource )) {
+ KURL::List urls;
+ QMap<QString,QString> metaData;
+ if( KURLDrag::decode( &aSource, urls, metaData )) {
+ // this is from KonqDrag (libkonq)
+ QByteArray a = aSource.encodedData( "application/x-kde-cutselection" );
+ bool cut = !a.isEmpty() && (a.at(0) == '1'); // true if 1
+ return new HistoryURLItem( urls, metaData, cut );
+ }
+ }
+ if ( QTextDrag::canDecode( &aSource ) ) {
+ QString text;
+ if( QTextDrag::decode( &aSource, text ))
+ return text.isNull() ? 0 : new HistoryStringItem( text );
+ }
+ if ( QImageDrag::canDecode( &aSource ) ) {
+ QPixmap image;
+ if( QImageDrag::decode( &aSource, image ))
+ return image.isNull() ? 0 : new HistoryImageItem( image );
+ }
+ return 0; // Failed.
+}
+
+HistoryItem* HistoryItem::create( QDataStream& aSource ) {
+ if ( aSource.atEnd() ) {
+ return 0;
+ }
+ QString type;
+ aSource >> type;
+ if ( type == "url" ) {
+ KURL::List urls;
+ QMap< QString, QString > metaData;
+ int cut;
+ aSource >> urls;
+ aSource >> metaData;
+ aSource >> cut;
+ return new HistoryURLItem( urls, metaData, cut );
+ }
+ if ( type == "string" ) {
+ QString text;
+ aSource >> text;
+ return new HistoryStringItem( text );
+ }
+ if ( type == "image" ) {
+ QPixmap image;
+ aSource >> image;
+ return new HistoryImageItem( image );
+ }
+ kdWarning() << "Failed to restore history item: Unknown type \"" << type << "\"" << endl;
+ return 0;
+}
+
diff --git a/klipper/historyitem.h b/klipper/historyitem.h
new file mode 100644
index 000000000..04ab542ed
--- /dev/null
+++ b/klipper/historyitem.h
@@ -0,0 +1,95 @@
+// -*- Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 8; -*-
+/* This file is part of the KDE project
+ Copyright (C) 2004 Esben Mose Hansen <[email protected]>
+
+ 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; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+#ifndef _HISTORYITEM_H_
+#define _HISTORYITEM_H_
+#include <qpixmap.h>
+
+class QString;
+class QMimeSource;
+class QDataStream;
+
+/**
+ * An entry in the clipboard history.
+ */
+class HistoryItem
+{
+public:
+ HistoryItem( );
+ virtual ~HistoryItem();
+
+ /**
+ * Return the current item as text
+ * An image would be returned as a descriptive
+ * text, such as 32x43 image.
+ */
+ virtual QString text() const = 0;
+
+ /**
+ * Return the current item as text
+ * A text would be returned as a null pixmap,
+ * which is also the default implementation
+ */
+ inline virtual const QPixmap& image() const;
+
+ /**
+ * Returns QMimeSource suitable for QClipboard::setData().
+ */
+ virtual QMimeSource* mimeSource() const = 0;
+
+ /**
+ * Write object on datastream
+ */
+ virtual void write( QDataStream& stream ) const = 0;
+
+ /**
+ * Equality.
+ */
+ virtual bool operator==(const HistoryItem& rhs) const = 0;
+
+ /**
+ * Create an HistoryItem from MimeSources (i.e., clipboard data)
+ * returns null if create fails (e.g, unsupported mimetype)
+ */
+ static HistoryItem* create( const QMimeSource& aSource );
+
+ /**
+ * Create an HistoryItem from MimeSources (i.e., clipboard data)
+ * returns null if creation fails. In this case, the datastream
+ * is left in an undefined state.
+ */
+ static HistoryItem* create( QDataStream& aSource );
+};
+
+inline
+const QPixmap& HistoryItem::image() const {
+ static QPixmap nullPixmap;
+ return nullPixmap;
+}
+
+inline
+QDataStream& operator<<( QDataStream& lhs, HistoryItem const * const rhs ) {
+ if ( rhs ) {
+ rhs->write( lhs );
+ }
+ return lhs;
+
+}
+
+#endif
diff --git a/klipper/historystringitem.cpp b/klipper/historystringitem.cpp
new file mode 100644
index 000000000..dcb7b8da9
--- /dev/null
+++ b/klipper/historystringitem.cpp
@@ -0,0 +1,31 @@
+// -*- Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 8; -*-
+/* This file is part of the KDE project
+ Copyright (C) 2004 Esben Mose Hansen <[email protected]>
+
+ 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; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+#include "historystringitem.h"
+
+HistoryStringItem::HistoryStringItem( const QString& data )
+ : HistoryItem(), m_data( data )
+{
+
+}
+
+/* virtual */
+void HistoryStringItem::write( QDataStream& stream ) const {
+ stream << QString( "string" ) << m_data;
+}
diff --git a/klipper/historystringitem.h b/klipper/historystringitem.h
new file mode 100644
index 000000000..ccbaa2e63
--- /dev/null
+++ b/klipper/historystringitem.h
@@ -0,0 +1,57 @@
+// -*- Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 8; -*-
+/* This file is part of the KDE project
+ Copyright (C) 2004 Esben Mose Hansen <[email protected]>
+
+ 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; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+#ifndef _HISTORYSTRINGITEM_H_
+#define _HISTORYSTRINGITEM_H_
+
+#include <qstring.h>
+#include <qdragobject.h>
+
+#include "historyitem.h"
+
+/**
+ * A string entry in the clipboard history.
+ */
+class HistoryStringItem : public HistoryItem
+{
+public:
+ HistoryStringItem( const QString& data );
+ virtual ~HistoryStringItem() {}
+ virtual QString text() const;
+ virtual bool operator==( const HistoryItem& rhs) const {
+ if ( const HistoryStringItem* casted_rhs = dynamic_cast<const HistoryStringItem*>( &rhs ) ) {
+ return casted_rhs->m_data == m_data;
+ }
+ return false;
+ }
+ virtual QMimeSource* mimeSource() const { return new QTextDrag( m_data ) ; }
+
+ /**
+ * Write object on datastream
+ */
+ virtual void write( QDataStream& stream ) const;
+
+private:
+ QString m_data;
+};
+
+inline QString HistoryStringItem::text() const { return m_data; }
+
+
+#endif
diff --git a/klipper/historyurlitem.cpp b/klipper/historyurlitem.cpp
new file mode 100644
index 000000000..3e85a17c3
--- /dev/null
+++ b/klipper/historyurlitem.cpp
@@ -0,0 +1,63 @@
+// -*- Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 8; -*-
+/* This file is part of the KDE project
+ Copyright (C) 2004 Esben Mose Hansen <[email protected]>
+
+ 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; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+#include "historyurlitem.h"
+
+#include <kmultipledrag.h>
+#include <kurldrag.h>
+
+HistoryURLItem::HistoryURLItem( const KURL::List &_urls, QMap<QString, QString> _metaData, bool _cut )
+ : urls( _urls ), metaData( _metaData ), cut( _cut )
+{
+}
+
+/* virtual */
+void HistoryURLItem::write( QDataStream& stream ) const
+{
+ stream << QString( "url" ) << urls << metaData << (int)cut;
+}
+
+QString HistoryURLItem::text() const {
+ return urls.toStringList().join( " " );
+}
+
+QMimeSource* HistoryURLItem::mimeSource() const {
+ KMultipleDrag* drag = new KMultipleDrag;
+ drag->addDragObject( new KURLDrag( urls, metaData ));
+ // from KonqDrag (libkonq)
+ QStoredDrag* cutdrag = new QStoredDrag( "application/x-kde-cutselection" );
+ QByteArray a;
+ QCString s ( cut ? "1" : "0" );
+ a.resize( s.length() + 1 ); // trailing zero
+ memcpy( a.data(), s.data(), s.length() + 1 );
+ cutdrag->setEncodedData( a );
+ drag->addDragObject( cutdrag );
+ return drag;
+}
+
+bool HistoryURLItem::operator==( const HistoryItem& rhs) const
+{
+ if ( const HistoryURLItem* casted_rhs = dynamic_cast<const HistoryURLItem*>( &rhs ) ) {
+ return casted_rhs->urls == urls
+ && casted_rhs->metaData.count() == metaData.count()
+ && qEqual( casted_rhs->metaData.begin(), casted_rhs->metaData.end(), metaData.begin())
+ && casted_rhs->cut == cut;
+ }
+ return false;
+}
diff --git a/klipper/historyurlitem.h b/klipper/historyurlitem.h
new file mode 100644
index 000000000..5619b6037
--- /dev/null
+++ b/klipper/historyurlitem.h
@@ -0,0 +1,49 @@
+// -*- Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 8; -*-
+/* This file is part of the KDE project
+ Copyright (C) 2004 Esben Mose Hansen <[email protected]>
+
+ 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; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+#ifndef _HISTORYURLITEM_H_
+#define _HISTORYURLITEM_H_
+
+#include "historyitem.h"
+
+#include <qmap.h>
+#include <kurl.h>
+
+/**
+ * An URL entry in the clipboard history.
+ */
+class HistoryURLItem : public HistoryItem
+{
+public:
+ HistoryURLItem( const KURL::List &urls, QMap<QString, QString> metaData, bool cut );
+ virtual QString text() const;
+ virtual bool operator==( const HistoryItem& rhs) const;
+ virtual QMimeSource* mimeSource() const;
+
+ /**
+ * Write object on datastream
+ */
+ virtual void write( QDataStream& stream ) const;
+private:
+ KURL::List urls;
+ QMap<QString, QString> metaData;
+ bool cut;
+};
+
+#endif
diff --git a/klipper/klipper-1-2.pl b/klipper/klipper-1-2.pl
new file mode 100644
index 000000000..b9cd337fb
--- /dev/null
+++ b/klipper/klipper-1-2.pl
@@ -0,0 +1,63 @@
+#!/usr/bin/perl
+
+my ($section, %data);
+
+#read in all the data, split it up into hashes. Thanks again to malte for much input
+while (<>) {
+ if (/\[(.*)\]/) {
+ $sections{$section} = {%data} if $section;
+ $section = $1;
+ undef %data;
+ next;
+ }
+ $data{$1} = $2 if /^([^=]*)=(.*)$/;
+}
+
+$sections{$section} = {%data} if $section;
+
+# not used up to now
+# $version = $sections{'General'}->{'Version'};
+
+# if "Action description" is not available, we have a new, fresh configuration
+# without any need for conversion.
+if ( ! $sections{'Action_0'}->{'Action description'} ) {
+ exit;
+}
+
+$numActions = $sections{'General'}->{'Number of Actions'};
+for my $i (0..($numActions - 1)) {
+ my $actionGroup = "Action_$i";
+ my $numCommands = $sections{$actionGroup}->{'Number of commands'};
+
+ print "[$actionGroup]\n";
+ # rename some keys
+ print "Description=$sections{$actionGroup}->{'Action description'}\n";
+ print "Regexp=$sections{$actionGroup}->{'Action regexp'}\n";
+ print "Number of commands=$numCommands\n";
+
+ # move the command entries from "Action_x" to "Action_x/Command_y"
+ for my $k (0..($numCommands - 1)) {
+ my $command = "Command_$k";
+ my $commandGroup = "$actionGroup/$command";
+ print "\n[$commandGroup]\n";
+ my $value = $sections{$actionGroup}->{"$command: commandline"};
+ print "Commandline=$value\n";
+ $value = $sections{$actionGroup}->{"$command: description"};
+ print "Description=$value\n";
+ $value = $sections{$actionGroup}->{"$command: enabled"};
+ print "Enabled=$value\n";
+ }
+ print "\n";
+}
+
+&copySection( "General" );
+&copySection( "Global Keys" );
+
+sub copySection()
+{
+ my ($group) = @_;
+ print "\n[$group]\n";
+ while (($key,$value) = each(%{$sections{$group}})) {
+ print "$key=$value\n";
+ }
+}
diff --git a/klipper/klipper-kde31.sh b/klipper/klipper-kde31.sh
new file mode 100644
index 000000000..755915fdc
--- /dev/null
+++ b/klipper/klipper-kde31.sh
@@ -0,0 +1,8 @@
+#!/bin/sh
+filename=`kde-config --localprefix`share/autostart/klipper.desktop
+if grep 'Hidden=true' "$filename" > /dev/null 2> /dev/null; then
+ echo AutoStart=false
+else
+ echo AutoStart=true
+fi
+rm -f "$filename" \ No newline at end of file
diff --git a/klipper/klipper.desktop b/klipper/klipper.desktop
new file mode 100644
index 000000000..4af6e5c9c
--- /dev/null
+++ b/klipper/klipper.desktop
@@ -0,0 +1,165 @@
+[Desktop Entry]
+Name=Klipper
+Name[bn]=ক্লিপার
+Name[eo]=Poŝilo
+Name[hi]=क्लिपर
+Name[kk]=Алмасу буфері
+Name[lo]=ຄິບເປີ້ - K
+Name[lv]=Klippers
+Name[mk]=Клипер
+Name[ne]=क्लिपर
+Name[pa]=ਕੇਲਿਪਰ
+Name[ru]=Монитор буфера обмена
+Name[ta]=க்ளிப்பர்
+Name[te]=క్లిప్పర్
+Name[th]=คลิปเปอร์
+Name[ven]=Kilipara
+Name[vi]=Bảng nháp
+Name[zh_TW]=剪貼薄(Klipper)
+GenericName=Clipboard Tool
+GenericName[af]=Klipbord Program
+GenericName[ar]=أداة الحافظة
+GenericName[be]=Буфер абмену
+GenericName[bg]=Системен буфер
+GenericName[bn]=ক্লিপবোর্ড টুল
+GenericName[br]=Ostilh ar golver
+GenericName[bs]=Spremnik
+GenericName[ca]=Eina de portapapers
+GenericName[cs]=Program pro práci se schránkou
+GenericName[csb]=Nôrzãdze tacnika
+GenericName[cy]=Offeryn Gludfwrdd
+GenericName[da]=Klippebordsværktøj
+GenericName[de]=Verwaltung der Zwischenablage
+GenericName[el]=Εργαλείο πρόχειρου
+GenericName[eo]=Ilo por la poŝo
+GenericName[es]=Portapapeles
+GenericName[et]=Lõikepuhvri haldamine
+GenericName[eu]=Arbela
+GenericName[fa]=ابزار تخته‌یادداشت
+GenericName[fi]=Leikepöytätyökalu
+GenericName[fr]=Presse-papiers
+GenericName[fy]=Klamboerdbehear
+GenericName[ga]=Uirlis Ghearrthaisce
+GenericName[gl]=Ferramenta de Portarretallos
+GenericName[he]=כלי לוח עריכה
+GenericName[hi]=क्लिपबोर्ड औज़ार
+GenericName[hr]=Alata međuspremnika
+GenericName[hu]=Vágólapkezelő
+GenericName[is]=Klippispjaldstól
+GenericName[it]=Strumento per gli appunti
+GenericName[ja]=クリップボードツール
+GenericName[ka]=გაცვლის ბუფერის უტილიტა
+GenericName[kk]=Алмасу буфер құралы
+GenericName[km]=ឧបករណ៍​ក្ដារ​តម្បៀត​ខ្ទាស់
+GenericName[ko]=클립보드 도구
+GenericName[lo]=ເຄື່ອງມືຄິບບອດ
+GenericName[lt]=Laikinos talpyklės priemonė
+GenericName[lv]=Starpliktuves Rīks
+GenericName[mk]=Алатка за табла со исечоци
+GenericName[mn]=Клипборд хэрэгсэл
+GenericName[ms]=Alat Klipbod
+GenericName[mt]=Għodda tal-klipbord
+GenericName[nb]=Utklippstavle
+GenericName[nds]=Warktüüch för de Twischenaflaag
+GenericName[ne]=क्लिपबोर्ड उपकरण
+GenericName[nl]=Klembordbeheer
+GenericName[nn]=Utklippstavle
+GenericName[nso]=Sebereka sa Clipboard
+GenericName[pa]=ਕਲਿੱਪਬੋਰਡ ਸੰਦ
+GenericName[pl]=Narzędzie schowka
+GenericName[pt]=Ferramenta da Área de Transferência
+GenericName[pt_BR]=Ferramenta de Área de Transferência
+GenericName[ro]=Utilitar de clipboard
+GenericName[ru]=Утилита для буфера обмена
+GenericName[rw]=Igikoresho Ububikokoporora
+GenericName[se]=Čuohpusgirjereaidu
+GenericName[sk]=Nástroj pre schránku
+GenericName[sl]=Orodje za odložišče
+GenericName[sr]=Алат за клипборд
+GenericName[sr@Latn]=Alat za klipbord
+GenericName[sv]=Klippbordsverktyg
+GenericName[ta]=தற்காலிக கருவி
+GenericName[te]=క్లిప్ బోర్డ్ పనిముట్టు
+GenericName[tg]=Обзори хотираи муваққат
+GenericName[th]=เครื่องมือคลิปบอร์ด
+GenericName[tr]=Pano Aracı
+GenericName[tt]=Kesä Qoralı
+GenericName[uk]=Утиліта кишені
+GenericName[uz]=Klipbord vositasi
+GenericName[uz@cyrillic]=Клипборд воситаси
+GenericName[ven]=Tshishumiswa tsha bodo yau nambatedza
+GenericName[vi]=Công cụ Bảng nháp
+GenericName[wa]=Usteye di tchapea emacralé
+GenericName[xh]=Isixhobo Sebhodi eqhoboshayo
+GenericName[zh_CN]=剪贴板工具
+GenericName[zh_TW]=剪貼簿工具
+GenericName[zu]=Ithuluzi Lebhodi elibophayo
+Exec=klipper
+Icon=klipper
+Type=Application
+DocPath=klipper/index.html
+Terminal=false
+X-KDE-autostart-after=panel
+X-KDE-StartupNotify=false
+X-DCOP-ServiceType=Unique
+X-KDE-Library=klipper_panelapplet
+X-KDE-UniqueApplet=true
+X-KDE-autostart-condition=klipperrc:General:AutoStart:true
+OnlyShowIn=KDE;
+Categories=Qt;KDE;Utility;X-KDE-Utilities-Desktop;
+Comment=A cut & paste history utility
+Comment[af]='n Knip & plak geskiedenis program
+Comment[ar]=أدات الخط الزمني للقص و الإلصاق
+Comment[be]=Службовая праграма працы з гісторыяй выразання/устаўкі
+Comment[bg]=Инструмент за управление на операциите е по копиране и поставяне
+Comment[bs]=Alat za historiju isijecanja i umetanja
+Comment[ca]=Una utilitat de la historia de tallar i enganxar
+Comment[cs]=Nástroj pro historii práce se schránkou
+Comment[csb]=Nôrzãdze trzëmôjące historëjã tacnika
+Comment[da]=Et værktøj med historik til at klippe ud og indsætte
+Comment[de]=Programm zur Verwaltung der Zwischenablage
+Comment[el]=Ένα εργαλείο ιστορικού αποκοπής & επικόλλησης
+Comment[eo]=Eltonda kaj alglua historia ilo
+Comment[es]=Una utilidad de cortar y pegar historial
+Comment[et]=Lõikamiste ja asetamiste ajaloo rakendus
+Comment[eu]=Ebaki eta itsasi-ren historian ibiltzeko tresna
+Comment[fa]=برنامۀ سودمند تاریخچۀ برش و چسباندن
+Comment[fi]=Työkalu leikkaamiseen ja liittämiseen
+Comment[fr]=Un outil d'historique du copier / coller
+Comment[fy]=Klamboerdhistoarje
+Comment[gl]=Un historial de cortar & pegar
+Comment[he]=שירותית היסטוריית גזירה והדבקה
+Comment[hr]=Alat za izrezivanje i preljepljivanje iz povijesti
+Comment[hu]=Kezelőprogram a vágólaphoz (naplózással)
+Comment[is]=Klipp & lím sögutól
+Comment[it]=Un programma che ricorda il copia e incolla
+Comment[ja]=カット&ペースト履歴ユーティリティ
+Comment[kk]=Қиып алу және орналастыру журналы
+Comment[km]=ឧបករណ៍​ប្រវត្តិ​កាត់ & បិទភ្ជាប់
+Comment[ko]=자르고 붙인 기록 도구
+Comment[lt]=„Iškirpti ir padėti“ istorijos pagalbinė programa
+Comment[mk]=Алатка за историјат на сечење и вметнување
+Comment[nb]=Et verktøy som viser tidligere utklipp
+Comment[nds]=Vörgeschicht vun't Knippen un Infögen
+Comment[ne]=काट्ने र टाँस्ने इतिहास युटिलिटी
+Comment[nl]=Klembordgeschiedenis
+Comment[nn]=Eit verktøy som viser eldre utklipp
+Comment[pa]=ਕੱਟਣ ਅਤੇ ਚੇਪਣ ਅਤੀਤ ਸਹੂਲਤ
+Comment[pl]=Narzędzie przechowujące historię schowka
+Comment[pt]=Um utilitário de histórico de cópias e colagens
+Comment[pt_BR]=Um utilitário com o histórico de cortar & colar
+Comment[ro]=Utilitar cu istoria de tăiere și adăugare
+Comment[ru]=История буфера обмена
+Comment[sk]=Vystrihnúť a vložiť históriu
+Comment[sl]=Orodje z zgodovino za izreži in prilepi
+Comment[sr]=Алат за историјат исецања и преношења
+Comment[sr@Latn]=Alat za istorijat isecanja i prenošenja
+Comment[sv]=Ett verktyg med historik för klipp ut och klistra in
+Comment[th]=โปรแกรมอรรถประโยชน์สำหรับดูประวัติการตัดและวาง
+Comment[tr]=Kes & yapıştır geçmişine erişim
+Comment[uk]=Утиліта історії кишені
+Comment[vi]=Công cụ chứa lịch sử các cắt dán
+Comment[wa]=Ene usteye po l' istwere des copyî/aclaper
+Comment[zh_CN]=剪贴历史工具
+Comment[zh_TW]=剪貼紀錄公用程式
+
diff --git a/klipper/klipperbindings.cpp b/klipper/klipperbindings.cpp
new file mode 100644
index 000000000..b32c90410
--- /dev/null
+++ b/klipper/klipperbindings.cpp
@@ -0,0 +1,36 @@
+// -*- Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 8; -*-
+/* This file is part of the KDE project
+ Copyright (C) by Andrew Stanley-Jones
+
+ 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; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+#ifndef NOSLOTS
+# define DEF( name, key3, key4, fnSlot ) \
+ keys->insert( name, i18n(name), QString::null, key3, key4, this, SLOT(fnSlot) )
+#else
+# define DEF( name, key3, key4, fnSlot ) \
+ keys->insert( name, i18n(name), QString::null, key3, key4 )
+#endif
+#define WIN KKey::QtWIN
+
+ keys->insert( "Program:klipper", i18n("Clipboard") );
+
+ DEF( I18N_NOOP("Show Klipper Popup-Menu"), ALT+CTRL+Qt::Key_V, WIN+CTRL+Qt::Key_V, slotPopupMenu() );
+ DEF( I18N_NOOP("Manually Invoke Action on Current Clipboard"), ALT+CTRL+Qt::Key_R, WIN+CTRL+Qt::Key_R, slotRepeatAction() );
+ DEF( I18N_NOOP("Enable/Disable Clipboard Actions"), ALT+CTRL+Qt::Key_X, WIN+CTRL+Qt::Key_X, toggleURLGrabber() );
+
+#undef DEF
+#undef WIN
diff --git a/klipper/klipperpopup.cpp b/klipper/klipperpopup.cpp
new file mode 100644
index 000000000..7dfc0a3fd
--- /dev/null
+++ b/klipper/klipperpopup.cpp
@@ -0,0 +1,304 @@
+// -*- Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 8; -*-
+/* This file is part of the KDE project
+ Copyright (C) 2004 Esben Mose Hansen <[email protected]>
+ Copyright (C) by Andrew Stanley-Jones
+ Copyright (C) 2000 by Carsten Pfeiffer <[email protected]>
+
+ 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; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+#include <kmessagebox.h>
+#include <khelpmenu.h>
+#include <kiconloader.h>
+#include <klineedit.h>
+
+#include <klocale.h>
+#include <kaction.h>
+#include <kglobalsettings.h>
+#include <kwin.h>
+#include <kapplication.h>
+#include <kglobalsettings.h>
+#include <kdebug.h>
+
+#include "klipperpopup.h"
+#include "history.h"
+#include "toplevel.h"
+#include "popupproxy.h"
+
+namespace {
+ static const int TOP_HISTORY_ITEM_INDEX = 2;
+}
+
+// #define DEBUG_EVENTS__
+
+#ifdef DEBUG_EVENTS__
+kdbgstream& operator<<( kdbgstream& stream, const QKeyEvent& e ) {
+ stream << "(QKeyEvent(text=" << e.text() << ",key=" << e.key() << ( e.isAccepted()?",accepted":",ignored)" ) << ",count=" << e.count();
+ if ( e.state() & Qt::AltButton ) {
+ stream << ",ALT";
+ }
+ if ( e.state() & Qt::ControlButton ) {
+ stream << ",CTRL";
+ }
+ if ( e.state() & Qt::MetaButton ) {
+ stream << ",META";
+ }
+ if ( e.state() & Qt::ShiftButton ) {
+ stream << ",SHIFT";
+ }
+ if ( e.isAutoRepeat() ) {
+ stream << ",AUTOREPEAT";
+ }
+ stream << ")";
+
+ return stream;
+}
+#endif
+
+/**
+ * Exactly the same as KLineEdit, except that ALL key events are swallowed.
+ *
+ * We need this to avoid infinite loop when sending events to the search widget
+ */
+class KLineEditBlackKey : public KLineEdit {
+public:
+ KLineEditBlackKey(const QString& string, QWidget* parent, const char* name )
+ : KLineEdit( string, parent, name )
+ {}
+
+ KLineEditBlackKey( QWidget* parent, const char* name )
+ : KLineEdit( parent, name )
+ {}
+
+ ~KLineEditBlackKey() {
+ }
+protected:
+ virtual void keyPressEvent( QKeyEvent* e ) {
+ KLineEdit::keyPressEvent( e );
+ e->accept();
+
+ }
+
+};
+
+KlipperPopup::KlipperPopup( History* history, QWidget* parent, const char* name )
+ : KPopupMenu( parent, name ),
+ m_dirty( true ),
+ QSempty( i18n( "<empty clipboard>" ) ),
+ QSnomatch( i18n( "<no matches>" ) ),
+ m_history( history ),
+ helpmenu( new KHelpMenu( this, KlipperWidget::aboutData(), false ) ),
+ m_popupProxy( 0 ),
+ m_filterWidget( 0 ),
+ m_filterWidgetId( 10 ),
+ n_history_items( 0 )
+{
+ KWin::WindowInfo i = KWin::windowInfo( winId(), NET::WMGeometry );
+ QRect g = i.geometry();
+ QRect screen = KGlobalSettings::desktopGeometry(g.center());
+ int menu_height = ( screen.height() ) * 3/4;
+ int menu_width = ( screen.width() ) * 1/3;
+
+ m_popupProxy = new PopupProxy( this, "popup_proxy", menu_height, menu_width );
+
+ connect( this, SIGNAL( aboutToShow() ), SLOT( slotAboutToShow() ) );
+}
+
+KlipperPopup::~KlipperPopup() {
+
+}
+
+void KlipperPopup::slotAboutToShow() {
+ if ( m_filterWidget ) {
+ if ( !m_filterWidget->text().isEmpty() ) {
+ m_dirty = true;
+ m_filterWidget->clear();
+ setItemVisible( m_filterWidgetId, false );
+ m_filterWidget->hide();
+ }
+ }
+ ensureClean();
+
+}
+
+void KlipperPopup::ensureClean() {
+ // If the history is unchanged since last menu build, the is no reason
+ // to rebuild it,
+ if ( m_dirty ) {
+ rebuild();
+ }
+
+}
+
+void KlipperPopup::buildFromScratch() {
+ m_filterWidget = new KLineEditBlackKey( this, "Klipper filter widget" );
+ insertTitle( SmallIcon( "klipper" ), i18n("Klipper - Clipboard Tool"));
+ m_filterWidgetId = insertItem( m_filterWidget, m_filterWidgetId, 1 );
+ m_filterWidget->setFocusPolicy( QWidget::NoFocus );
+ setItemVisible( m_filterWidgetId, false );
+ m_filterWidget->hide();
+ QString lastGroup;
+
+ // Bit of a hack here. It would be better of KHelpMenu could be an action.
+ // Insert Help-menu at the butttom of the "default" group.
+ QString group;
+ QString defaultGroup( "default" );
+ for ( KAction* action = m_actions.first(); action; action = m_actions.next() ) {
+ group = action->group();
+ if ( group != lastGroup ) {
+ if ( lastGroup == defaultGroup ) {
+ insertItem( SmallIconSet("help"), KStdGuiItem::help().text(), helpmenu->menu() );
+ }
+ insertSeparator();
+ }
+ lastGroup = group;
+ action->plug( this, -1 );
+ }
+
+ if ( KGlobalSettings::insertTearOffHandle() ) {
+ insertTearOffHandle();
+ }
+
+}
+
+void KlipperPopup::rebuild( const QString& filter ) {
+
+ bool from_scratch = ( count() == 0 );
+ if ( from_scratch ) {
+ buildFromScratch();
+ } else {
+ for ( int i=0; i<n_history_items; i++ ) {
+ removeItemAt( TOP_HISTORY_ITEM_INDEX );
+ }
+ }
+
+ QRegExp filterexp( filter );
+ if ( filterexp.isValid() ) {
+ m_filterWidget->setPaletteForegroundColor( paletteForegroundColor() );
+ } else {
+ m_filterWidget->setPaletteForegroundColor( QColor( "red" ) );
+ }
+ n_history_items = m_popupProxy->buildParent( TOP_HISTORY_ITEM_INDEX, filterexp );
+
+ if ( n_history_items == 0 ) {
+ if ( m_history->empty() ) {
+ insertItem( QSempty, -1, TOP_HISTORY_ITEM_INDEX );
+ } else {
+ insertItem( QSnomatch, -1, TOP_HISTORY_ITEM_INDEX );
+ }
+ n_history_items++;
+ } else {
+ if ( history()->topIsUserSelected() ) {
+ int id = idAt( TOP_HISTORY_ITEM_INDEX );
+ if ( id != -1 ) {
+ setItemChecked( id,true );
+ }
+ }
+ }
+
+
+ m_dirty = false;
+
+}
+
+void KlipperPopup::plugAction( KAction* action ) {
+ m_actions.append( action );
+}
+
+
+
+
+/* virtual */
+void KlipperPopup::keyPressEvent( QKeyEvent* e ) {
+ // If alt-something is pressed, select a shortcut
+ // from the menu. Do this by sending a keyPress
+ // without the alt-modifier to the superobject.
+ if ( e->state() & Qt::AltButton ) {
+ QKeyEvent ke( QEvent::KeyPress,
+ e->key(),
+ e->ascii(),
+ e->state() ^ Qt::AltButton,
+ e->text(),
+ e->isAutoRepeat(),
+ e->count() );
+ KPopupMenu::keyPressEvent( &ke );
+#ifdef DEBUG_EVENTS__
+ kdDebug() << "Passing this event to ancestor (KPopupMenu): " << e "->" << ke << endl;
+#endif
+ if ( ke.isAccepted() ) {
+ e->accept();
+ return;
+ } else {
+ e->ignore();
+ }
+ }
+
+ // Otherwise, send most events to the search
+ // widget, except a few used for navigation:
+ // These go to the superobject.
+ switch( e->key() ) {
+ case Qt::Key_Up:
+ case Qt::Key_Down:
+ case Qt::Key_Right:
+ case Qt::Key_Left:
+ case Qt::Key_Tab:
+ case Qt::Key_Backtab:
+ case Qt::Key_Escape:
+ case Qt::Key_Return:
+ case Qt::Key_Enter:
+ {
+#ifdef DEBUG_EVENTS__
+ kdDebug() << "Passing this event to ancestor (KPopupMenu): " << e << endl;
+#endif
+ KPopupMenu::keyPressEvent( e );
+ if ( isItemActive( m_filterWidgetId ) ) {
+ setActiveItem( TOP_HISTORY_ITEM_INDEX );
+ }
+ break;
+ }
+ default:
+ {
+#ifdef DEBUG_EVENTS__
+ kdDebug() << "Passing this event down to child (KLineEdit): " << e << endl;
+#endif
+ QString lastString = m_filterWidget->text();
+ QApplication::sendEvent( m_filterWidget, e );
+ if ( m_filterWidget->text().isEmpty() ) {
+ if ( isItemVisible( m_filterWidgetId ) )
+ {
+ setItemVisible( m_filterWidgetId, false );
+ m_filterWidget->hide();
+ }
+ }
+ else if ( !isItemVisible( m_filterWidgetId ) )
+ {
+ setItemVisible( m_filterWidgetId, true );
+ m_filterWidget->show();
+
+ }
+ if ( m_filterWidget->text() != lastString) {
+ slotHistoryChanged();
+ rebuild( m_filterWidget->text() );
+ }
+ break;
+
+ } //default:
+ } //case
+
+}
+
+
+
+#include "klipperpopup.moc"
diff --git a/klipper/klipperpopup.h b/klipper/klipperpopup.h
new file mode 100644
index 000000000..f77b87954
--- /dev/null
+++ b/klipper/klipperpopup.h
@@ -0,0 +1,127 @@
+// -*- Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 8; -*-
+/* This file is part of the KDE project
+ Copyright (C) 2004 Esben Mose Hansen <[email protected]>
+ Copytight (C) by Andrew Stanley-Jones
+ 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; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+#ifndef _KLIPPERPOPUP_H_
+#define _KLIPPERPOPUP_H_
+
+#include <kpopupmenu.h>
+#include <qptrlist.h>
+#include <qstring.h>
+
+class History;
+class KlipperWidget;
+class KHelpMenu;
+class KAction;
+class PopupProxy;
+class KLineEdit;
+
+/**
+ * Default view of clipboard history.
+ *
+ */
+class KlipperPopup : public KPopupMenu
+{
+ Q_OBJECT
+
+public:
+ KlipperPopup( History* history, QWidget* parent=0, const char* name=0 );
+ ~KlipperPopup();
+ void plugAction( KAction* action );
+
+ /**
+ * Normally, the popupmenu is only rebuilt just before showing.
+ * If you need the pixel-size or similar of the this menu, call
+ * this beforehand.
+ */
+ void ensureClean();
+
+ History* history() { return m_history; }
+ const History* history() const { return m_history; }
+
+public slots:
+ void slotHistoryChanged() { m_dirty = true; }
+ void slotAboutToShow();
+
+private:
+ void rebuild( const QString& filter = QString::null );
+ void buildFromScratch();
+
+ void insertSearchFilter();
+ void removeSearchFilter();
+
+protected:
+ virtual void keyPressEvent( QKeyEvent* e );
+
+private:
+ bool m_dirty : 1; // true if menu contents needs to be rebuild.
+
+ /**
+ * Contains the string shown if the menu is empty.
+ */
+ QString QSempty;
+
+ /**
+ * Contains the string shown if the search string has no
+ * matches and the menu is not empty.
+ */
+ QString QSnomatch;
+
+ /**
+ * The "document" (clipboard history)
+ */
+ History* m_history;
+
+ /**
+ * The help menu
+ */
+ KHelpMenu* helpmenu;
+
+ /**
+ * (unowned) actions to plug into the primary popup menu
+ */
+ QPtrList<KAction> m_actions;
+
+ /**
+ * Proxy helper object used to track history items
+ */
+ PopupProxy* m_popupProxy;
+
+ /**
+ * search filter widget
+ */
+ KLineEdit* m_filterWidget;
+
+ /**
+ * id of search widget, for convenience
+ */
+ int m_filterWidgetId;
+
+ /**
+ * The current number of history items in the clipboard
+ */
+ int n_history_items;
+
+signals:
+ void clearHistory();
+ void configure();
+ void quit();
+
+};
+
+#endif
diff --git a/klipper/klipperrc.desktop b/klipper/klipperrc.desktop
new file mode 100644
index 000000000..ba04cf594
--- /dev/null
+++ b/klipper/klipperrc.desktop
@@ -0,0 +1,2066 @@
+[Action_0]
+Description=Jpeg-Image
+Description[af]=Jpeg Beeld
+Description[ar]=صورة-Jpeg
+Description[az]=Jpeg-Rəsmi
+Description[be]=Відарыс JPEG
+Description[bg]=Изображение Jpeg
+Description[bn]=জেপেগ চিত্র
+Description[br]=Skeudenn Jpeg
+Description[bs]=Jpeg-slika
+Description[ca]=Imatge jpeg
+Description[cs]=Obrázek JPEG
+Description[csb]=Òbrôz Jpeg
+Description[cy]=Delwedd Jpeg
+Description[da]=JPEG-billede
+Description[de]=JPEG-Image
+Description[el]=Εικόνα-Jpeg
+Description[eo]=JPEG-grafiko
+Description[es]=Imagen Jpeg
+Description[et]=Jpeg pildifail
+Description[eu]=Jpeg irudia
+Description[fa]=تصویر Jpeg
+Description[fi]=Jpeg-kuva
+Description[fo]=JPEG-mynd
+Description[fr]=Image JPEG
+Description[fy]=Jpeg-ôfbylding
+Description[ga]=Íomhá JPEG
+Description[gl]=Imaxe Jpeg
+Description[he]=תמונת JPEG
+Description[hi]=जेपीईजी-छवि
+Description[hr]=Jpeg slika
+Description[hu]=JPEG kép
+Description[is]=Jpeg mynd
+Description[it]=Immagine JPEG
+Description[ja]=Jpeg 画像
+Description[ka]=jpeg გამოსახულება
+Description[kk]=Jpeg-кескіні
+Description[km]=រូបភាព Jpeg
+Description[ko]=Jpeg-그림
+Description[lo]=ແຟ້ມພາບ Jpeg
+Description[lt]=Jpeg paveiksliukas
+Description[lv]=Jpeg Attēls
+Description[mk]=Jpeg-слика
+Description[mn]=JPEG-Зураг
+Description[ms]=Imej Jpeg
+Description[mt]=Stampa JPEG
+Description[nb]=Jpeg-bilde
+Description[nds]=JPEG-Bild
+Description[ne]=Jpeg-छवि
+Description[nl]=Jpeg-afbeelding
+Description[nn]=Jpeg-bilete
+Description[nso]=Ponagalo ya Jpeg
+Description[pa]=Jpeg-ਚਿੱਤਰ
+Description[pl]=Obrazek Jpeg
+Description[pt]=Imagem-JPEG
+Description[pt_BR]=Imagem JPEG
+Description[ro]=Imagine JPEG
+Description[ru]=Рисунок JPEG
+Description[rw]=Ishusho-jpeg
+Description[se]=Jpeg-govva
+Description[sk]=Obrázok Jpeg
+Description[sl]=Slika JPEG
+Description[sr]=Jpeg слика
+Description[sr@Latn]=Jpeg slika
+Description[sv]=Jpeg-bild
+Description[ta]=Jpeg-பிம்பம்
+Description[te]=జెపిఈజి చిత్రం
+Description[tg]=Тасвири Jpeg
+Description[th]=แฟ้มภาพ Jpeg
+Description[tr]=Jpeg Resmi
+Description[tt]=Jpeg-Sürät
+Description[uk]=Зображення Jpeg
+Description[uz]=Jpeg-rasm
+Description[uz@cyrillic]=Jpeg-расм
+Description[ven]=Tshifanyiso tsha jpeg
+Description[vi]=Hình Jpeg
+Description[wa]=Imådje JPEG
+Description[xh]=Jpeg-Umfanekiso
+Description[zh_CN]=JPEG 图像
+Description[zh_TW]=JPEG 影像
+Description[zu]=Isithombe se-Jpeg
+Number of commands=1
+Regexp=^\\/.+\\.jpg$
+
+[Action_0/Command_0]
+Commandline=kview %s
+Description=Launch K&View
+Description[af]=Lanseer K-bekyk
+Description[ar]=أ&طلق KView
+Description[az]=K&View-u Başlat
+Description[be]=Запусціць K&View
+Description[bg]=&Стартиране на KView
+Description[bn]=কে-ভি&উ চালাও
+Description[br]=Lañsañ K&View
+Description[bs]=Pokreni K&View
+Description[ca]=Engega el K&View
+Description[cs]=Spustit K&View
+Description[csb]=Zrëszë &Przezérnik òbrôzów
+Description[cy]=Cychwyn K&View
+Description[da]=Start K&View
+Description[de]=K&View starten
+Description[el]=Εκκίνηση K&View
+Description[eo]=Lanĉi &rigardilon
+Description[es]=Iniciar K&View
+Description[et]=K&View käivitamine
+Description[eu]=Abiarazi K&View
+Description[fa]=راه‌اندازیK&View‌
+Description[fi]=Käynnistä K&View
+Description[fr]=Démarrer K&View
+Description[fy]=K&View útfiere
+Description[ga]=Tosaigh K&View
+Description[gl]=Executar K&View
+Description[he]=הפעל את &KView
+Description[hi]=के-व्यू चलाएँ (&V)
+Description[hr]=Pokreni K&View
+Description[hu]=A K&View indítása
+Description[is]=Ræsa K&View
+Description[it]=Avvia K&View
+Description[ja]=KView を起動(&V)
+Description[ka]=K&View გაშვება
+Description[kk]=K&View жегу
+Description[km]=បើក K&View
+Description[lo]=ຮງກທຳງານ K&View
+Description[lt]=Paleisti K&View
+Description[lv]=Palaist K&Skatīt
+Description[mk]=Стартувај K&View
+Description[mn]=K&View эхлүүлэх
+Description[ms]=Lancarkan K&View
+Description[mt]=Ħaddem K&View
+Description[nb]=Start K&View
+Description[nds]=K&View starten
+Description[ne]=के दृश्य सुरुआत गर्नुहोस्
+Description[nl]=K&View opstarten
+Description[nn]=Start K&View
+Description[nso]=Ngwadisaleswa K&Pono
+Description[pa]=ਕੇ-ਵਿਊ ਜਾਰੀ(&V)
+Description[pl]=Uruchom &Przeglądarkę obrazków
+Description[pt]=Lançar o K&View
+Description[pt_BR]=Lançar K&View
+Description[ro]=Execută K&View
+Description[ru]=Запустить K&View
+Description[rw]=Gutangiza K-Kugaragaza
+Description[se]=Álggat K&View
+Description[sk]=Spustiť K&View
+Description[sl]=Zaženi K&View
+Description[sr]=Покрени K&View
+Description[sr@Latn]=Pokreni K&View
+Description[sv]=Starta K&view
+Description[ta]=KViewவை இறக்கு
+Description[tg]=Ба кор даровардани K&View
+Description[th]=เรียกใช้งาน K&View
+Description[tr]=K&View Çalıştır
+Description[tt]=K&View Cibärü
+Description[uk]=Запустити K&View
+Description[uz]=K&View'ni ishga tushirish
+Description[uz@cyrillic]=K&View'ни ишга тушириш
+Description[ven]=Thoma K&Vhona
+Description[vi]=Chạy K&View
+Description[wa]=Enonder K&View
+Description[zh_CN]=调用 K&View
+Description[zh_TW]=啟動 K&View
+Description[zu]=Qalisa i-K&View
+Enabled=true
+
+[Action_1]
+Description=Web-URL
+Description[ar]=عنوان مورد نظامي على الشبكة
+Description[az]=Veb-URL
+Description[be]=Спасылка Сеціва
+Description[bg]=Уеб адрес
+Description[bn]=ওয়েব ইউ-আর-এল
+Description[br]=URL-gwiad
+Description[ca]=URL web
+Description[cs]=Webové URL
+Description[csb]=Adresa WWW
+Description[cy]=URL Gw?
+Description[da]=Net-URL
+Description[el]=URL-Ιστού
+Description[eo]=TTT-URL
+Description[es]=URL Web
+Description[et]=Veebi-URL
+Description[eu]=Web URLa
+Description[fa]=نشانی وب وب
+Description[fr]=URL web
+Description[fy]=Web-URL-adres
+Description[he]=כתובת אינטרנט
+Description[hi]=वेब-यूआरएल
+Description[hu]=Webes URL
+Description[is]=Vefslóð
+Description[it]=URL Web
+Description[ja]=ウェブ URL
+Description[ka]=ვებ ბმული
+Description[kk]=URL сілтемесі
+Description[km]=URL បណ្ដាញ
+Description[lo]=ຕຳແຫນ່ງ URL ຂອງເວ໊ບ
+Description[mk]=Веб-URL
+Description[mn]=Вэб-URL
+Description[ms]=URL Laman
+Description[mt]=URL fuq il-web
+Description[nb]=Internettadresse
+Description[nds]=Nett-URL
+Description[ne]=वेब-यूआरएल
+Description[nl]=Web-URL-adres
+Description[nn]=Internettadresse
+Description[nso]=URL ya Web
+Description[pa]=ਵੈੱਬ-URL
+Description[pl]=Adres WWW
+Description[pt]=URL da Web
+Description[pt_BR]=URL
+Description[ro]=URL web
+Description[ru]=Ссылка на веб-страницу
+Description[rw]=URL-Urubugamakuru
+Description[se]=Fierpmádatčujuhus
+Description[sl]=Spletni URL
+Description[sr]=Веб URL
+Description[sr@Latn]=Veb URL
+Description[sv]=Webbadress
+Description[ta]=வலை-வலைப்பின்னல்
+Description[te]=వెబ్ యూ ఆర్ ఎల్
+Description[tg]=Нишони Вэб
+Description[th]=ตำแหน่ง URL ของเว็บ
+Description[uk]=URL Тенет
+Description[uz]=Veb-URL
+Description[uz@cyrillic]=Веб-URL
+Description[ven]=Webe ya URL
+Description[vi]=URL trên mạng
+Description[wa]=Hårdêye waibe
+Description[zh_CN]=Web URL
+Description[zh_TW]=網頁 URL
+Description[zu]=I-URL ye-Web
+Number of commands=5
+Regexp=^https?://.
+
+[Action_1/Command_0]
+Commandline=kfmclient exec %s
+Description=Open with &Konqueror
+Description[af]=Maak oop met Konqueror
+Description[ar]=إفتح بــ &Konqueror
+Description[az]=&Konqueror ilə Aç
+Description[be]=Адкрыць у &Konqueror
+Description[bg]=&Отваряне с Konqueror
+Description[bn]=কনকরার দিয়ে খোলো (&ক)
+Description[br]=Digeriñ gant &Konqueror
+Description[bs]=Otvori u &Konqueroru
+Description[ca]=Obre amb el &Konqueror
+Description[cs]=Otevřít pomocí &Konqueroru
+Description[csb]=Òtemknij w &Konquerorze
+Description[cy]=Agor gyda &Konqueror
+Description[da]=Åbn med &Konqueror
+Description[de]=Mit &Konqueror öffnen
+Description[el]=Άνοιγμα με το &Konqueror
+Description[eo]=Malfermi per &Konkeranto
+Description[es]=Abrir con &Konqueror
+Description[et]=&Konqueroris avamine
+Description[eu]= Ireki &Konquerorrekin
+Description[fa]=باز کردن با&Konqueror‌
+Description[fi]=Avaa &Konquerorissa
+Description[fo]=Opna við &Konqueror
+Description[fr]=Ouvrir dans &Konqueror
+Description[fy]=Iepenje mei &Konqueror
+Description[ga]=Oscail le &Konqueror
+Description[gl]=Abrir con &Konqueror
+Description[he]=פתח באמצעות &Konqueror
+Description[hi]=कॉन्करर के साथ खोलें (&K)
+Description[hr]=Otvori pomoću &Konquerora
+Description[hu]=Megnyitás a &Konquerorral
+Description[is]=Opna með &Konqueror
+Description[it]=Apri con &Konqueror
+Description[ja]=Konqueror で開く(&K)
+Description[ka]=&Konqueror-ში გახსნა
+Description[kk]=&Konqueror-де ашу
+Description[km]=បើក​ជាមួយ &Konqueror
+Description[ko]=Konqueror로 열기(&K)
+Description[lo]=ເປີດດ້ວຍຄອນຄິວເຣ &Konquerorີ
+Description[lt]=Atverti su &Konqueror
+Description[lv]=Atvērt ar Ie&karotāju
+Description[mk]=Отвори со &Konqueror
+Description[mn]=&Конкюророор нээх
+Description[ms]=Buka dengan &Konqueror
+Description[mt]=Iftaħ b' &Konqueror
+Description[nb]=Åpne med &Konqueror
+Description[nds]=Mit &Konqueror opmaken
+Description[ne]=कन्क्वेररसँग खोल्नुहोस्
+Description[nl]=Openen met &Konqueror
+Description[nn]=Opna med &Konqueror
+Description[nso]=Bula ka &Konqueror
+Description[pa]=ਕੋਨਕਿਉਰੋਰ ਨਾਲ ਖੋਲੋ(&K)
+Description[pl]=Otwórz w &Konquerorze
+Description[pt]=Abrir com o &Konqueror
+Description[pt_BR]=Abrir com o &Konqueror
+Description[ro]=Deschide cu &Konqueror
+Description[ru]=Открыть в &Konqueror
+Description[rw]=Gufungura ukoresheje Konqueror
+Description[se]=Raba &Konquerorain
+Description[sk]=Otvoriť pomocou &Konqueror
+Description[sl]=Odpri s &Konquerorjem
+Description[sr]=Отвори помоћу &Konqueror-а
+Description[sr@Latn]=Otvori pomoću &Konqueror-a
+Description[sv]=Öppna med &Konqueror
+Description[ta]=கான்கொரர் உடன் திற
+Description[te]=(&K) కాంకెరర్ తొ తెరువు
+Description[tg]=Бо &Konqueror боз кунед
+Description[th]=เปิดด้วย&คอนเควอร์เรอร์
+Description[tr]=&Konqueror ile Aç
+Description[tt]=&Konqueror belän Aç
+Description[uk]=Відкрити з &Konqueror
+Description[uz]=Konqueror &bilan ochish
+Description[uz@cyrillic]=Konqueror &билан очиш
+Description[ven]=Vula nga &Konqueror
+Description[vi]=Mở bằng &Konqueror
+Description[wa]=Drovi avou &Konqueror
+Description[xh]=Vula nge &Konqueror
+Description[zh_CN]=用 &Konqueror 打开
+Description[zh_TW]=使用 &Konqueror 開啟
+Description[zu]=Vula nge-&Konqueror
+Enabled=true
+
+[Action_1/Command_1]
+Commandline=ps x |grep -q '[m]ozilla' && mozilla -remote openURL\(%s, new-window\) || mozilla %s
+Icon=mozilla
+Description=Open with &Mozilla
+Description[af]=Maak oop met Mozilla
+Description[ar]=إفتح بِــ &Mozilla
+Description[az]=&Mozilla ilə Aç
+Description[be]=Адкрыць у &Mozilla
+Description[bg]=От&варяне с Mozilla
+Description[bn]=মোজিলা দিয়ে খোলো (&ম)
+Description[br]=Digeriñ gant &Mozilla
+Description[bs]=Otvori u &Mozilla
+Description[ca]=Obre amb el &Mozilla
+Description[cs]=Otevřít pomocí &Mozilla
+Description[csb]=Òtemknij w &Mozillë
+Description[cy]=Agor gyda &Mozilla
+Description[da]=Åbn med &Mozilla
+Description[de]=Mit &Mozilla öffnen
+Description[el]=Άνοιγμα με το &Mozilla
+Description[eo]=Malfermi per &Mozilo
+Description[es]=Abrir con &Mozilla
+Description[et]=&Mozillas avamine
+Description[eu]=Ireki &Mozillarekin
+Description[fa]=باز کردن با &موزیلا‌
+Description[fi]=Avaa &Mozillalla
+Description[fo]=Opna við &Mozilla
+Description[fr]=Ouvrir dans &Mozilla
+Description[fy]=Iepenje mei &Mozilla
+Description[ga]=Oscail le &Mozilla
+Description[gl]=Abrir con &Mozilla
+Description[he]=פתח באמצעות &Mozilla
+Description[hi]=मोज़िला के साथ खोलें (&M)
+Description[hr]=Otvori pomoću &Mozille
+Description[hu]=Megnyitás a &Mozillával
+Description[is]=Opna með &Mozilla
+Description[it]=Apri con &Mozilla
+Description[ja]=Mozilla で開く(&M)
+Description[ka]=&Mozilla-ში გასნა
+Description[kk]=&Mozilla-да ашу
+Description[km]=បើក​ជាមួយ Mozilla
+Description[ko]=Mozilla로 열기(&M)
+Description[lo]=ເປີດດ້ວຍ &Mozilla
+Description[lt]=Atverti su &Mozilla
+Description[lv]=Atvērt ar &Mozilla
+Description[mk]=Отвори со &Mozilla
+Description[mn]=&Mозиллагаар нээх
+Description[ms]=Buka dengan &Mozilla
+Description[mt]=Iftaħ b' &Możilla
+Description[nb]=Åpne med &Mozilla
+Description[nds]=Mit &Mozilla opmaken
+Description[ne]=मोजिलासँग खोल्नुहोस्
+Description[nl]=Openen met &Mozilla
+Description[nn]=Opna med &Mozilla
+Description[nso]=Bula ka &Mozilla
+Description[pa]=ਮੌਜੀਲਾ ਨਾਲ ਖੋਲੋ(&M)
+Description[pl]=Otwórz w &Mozilli
+Description[pt]=Abrir com o &Mozilla
+Description[pt_BR]=Abrir com o &Mozilla
+Description[ro]=Deschide cu &Mozilla
+Description[ru]=Открыть в &Mozilla
+Description[rw]=Gufungura ukoresheje Mozilla
+Description[se]=Raba &Mozillain
+Description[sk]=Otvoriť pomocou &Mozilla
+Description[sl]=Odpri z &Mozillo
+Description[sr]=Отвори помоћу &Mozilla-е
+Description[sr@Latn]=Otvori pomoću &Mozilla-e
+Description[sv]=Öppna med &Mozilla
+Description[ta]=மொசில்லாவுடன் திற
+Description[te]=(&M) మొజిల్లా తొ తెరువు
+Description[tg]=Бо &Mozilla боз кунед
+Description[th]=เปิดด้วย&มอซซิลลา
+Description[tr]=&Mozilla ile Aç
+Description[tt]=&Mozilla belän Aç
+Description[uk]=Відкрити з &Mozilla
+Description[uz]=Mozilla bilan &ochish
+Description[uz@cyrillic]=Mozilla билан &очиш
+Description[ven]=Vula nga &Mozilla
+Description[vi]=Mở bằng &Mozilla
+Description[wa]=Drovi avou &Mozilla
+Description[xh]=Vula nge &Mozilla
+Description[zh_CN]=用 &Mozilla 打开
+Description[zh_TW]=使用 &Mozilla 開啟
+Description[zu]=Vula nge-&Mozilla
+Enabled=true
+
+[Action_1/Command_2]
+Commandline=kmail --body %s
+Description=Send &URL
+Description[af]=Stuur URL
+Description[ar]=ارسال &رابط
+Description[az]=&URL Göndər
+Description[be]=Даслаць &URL
+Description[bg]=&Изпращане на адрес
+Description[bn]=ই&উ-আর-এল পাঠাও
+Description[br]=Kas an &URL
+Description[bs]=Pošalji &URL
+Description[ca]=Envia l'&URL
+Description[cs]=Poslat &URL
+Description[csb]=Wëslij adresã (&URL)
+Description[cy]=Anfon &URL
+Description[de]=&URL senden
+Description[el]=Αποστολή &URL
+Description[eo]=Sendi &URLon
+Description[es]=Enviar &URL
+Description[et]=&URL-i saatmine
+Description[eu]=Bidali &URLa
+Description[fa]=ارسال &نشانی ‌وب‌
+Description[fi]=Lähetä &URL
+Description[fr]=Envoyer l'&URL
+Description[fy]=&URL-adres stjoere
+Description[ga]=Seol &URL
+Description[gl]=Enviar &URL
+Description[he]=שלח &כתובת
+Description[hi]= यूआरएल भेजें (&U)
+Description[hr]=Pošalji &URL
+Description[hu]=Az &URL elküldése
+Description[is]=Senda &slóð
+Description[it]=Invia l'&URL
+Description[ja]=URL を送信(&U)
+Description[ka]=&URL-ს გაგზავნა
+Description[kk]=&URL-ды жіберу
+Description[km]=ផ្ញើ &URL
+Description[lo]=ສັ່ງຕຳແຫນ່ງ &URL
+Description[lt]=Siųsti &URL
+Description[lv]=Sūtīt &URL
+Description[mk]=Испрати &URL
+Description[mn]=&URL илгээх
+Description[ms]=Hantar &URL
+Description[mt]=Ibgħat &URL
+Description[nb]=Send &Nettadressen
+Description[nds]=&URL schicken
+Description[ne]=यूआरएल पठाउनुहोस्
+Description[nl]=&URL-adres verzenden
+Description[nso]=Romela &URL
+Description[pa]=&URL ਭੇਜੋ
+Description[pl]=Wyślij adres (&URL)
+Description[pt]=Enviar o &URL
+Description[pt_BR]=Enviar &URL
+Description[ro]=Trimite &URL-ul
+Description[ru]=Отправить &URL
+Description[rw]=Kohereza URL
+Description[se]=Sádde &URL:a
+Description[sk]=Poslať &URL
+Description[sl]=Pošlji &URL
+Description[sr]=Пошаљи &URL
+Description[sr@Latn]=Pošalji &URL
+Description[sv]=Skicka &webbadress
+Description[ta]=வலைப்பின்னலை அனுப்பு
+Description[te]=(&U) యూ ఆర్ ఎల్ పంపించు
+Description[tg]=Фиристодани &URL
+Description[th]=ส่งตำแหน่ง &URL
+Description[tr]=&URL Gönder
+Description[tt]=&URL Künderü
+Description[uk]=Відіслати &URL
+Description[uz]=URL'ni &joʻnatish
+Description[uz@cyrillic]=URL'ни &жўнатиш
+Description[ven]=Rumele &URL
+Description[vi]=Gửi&URL
+Description[wa]=Evoyî &hårdêye
+Description[xh]=Thumela i &URL
+Description[zh_CN]=发送 &URL
+Description[zh_TW]=送出 &URL
+Description[zu]=Thumela i-&URL
+Enabled=true
+
+[Action_1/Command_3]
+Commandline=ps x |grep -q '[f]irefox' && firefox -remote openURL\(%s, new-window\) || firefox %s
+Icon=firefox
+Description=Open with &Firefox
+Description[af]=Maak oop met Firefox
+Description[ar]=إفتح بِــ &Firefox
+Description[be]=Адкрыць у &Konqueror
+Description[bg]=Отв&аряне с Firefox
+Description[bn]=ফায়ার&ফক্স দিয়ে খোলো
+Description[br]=Digeriñ gant &Firefox
+Description[bs]=Otvori u &Firefoxu
+Description[ca]=Obre amb el &Firefox
+Description[cs]=Otevřít pomocí &Firefox
+Description[csb]=Òtemknij w &Firefokse
+Description[da]=Åbn med &Firefox
+Description[de]=Mit &Firefox öffnen
+Description[el]=Άνοιγμα με το &Firefox
+Description[eo]=Malfermi per &Fajrovulpo
+Description[es]=Abrir con &Firefox
+Description[et]=&Firefoxis avamine
+Description[eu]=Ireki &Firefox-ekin
+Description[fa]=باز کردن با &فایرفاکس‌
+Description[fi]=Avaa &Firefoxissa
+Description[fr]=Ouvrir dans &Firefox
+Description[fy]=Iepenje mei &Firefox
+Description[ga]=Oscail le &Firefox
+Description[gl]=Abrir con &Firefox
+Description[he]=פתח באמצעות &Firefox
+Description[hr]=Otvori pomoću &Firefoxa
+Description[hu]=Megnyitás a &Firefoxszal
+Description[is]=Opna með &Firefox
+Description[it]=Apri con &Firefox
+Description[ja]=Firefox で開く(&F)
+Description[ka]=გახსნა &Firefox-ით
+Description[kk]=&Firefox шолғышта ашу
+Description[km]=បើក​ជាមួយ &Firefox
+Description[ko]=Firefox로 열기(&F)
+Description[lt]=Atverti su &Firefox
+Description[mk]=Отвори со &Firefox
+Description[nb]=Åpne med &Firefox
+Description[nds]=Mit &Firefox opmaken
+Description[ne]=फायरफक्ससँग खोल्नुहोस्
+Description[nl]=Openen met &Firefox
+Description[nn]=Opna med &Firefox
+Description[pa]=ਫਾਇਰਫਾਕਸ ਨਾਲ ਖੋਲੋ(&F)
+Description[pl]=Otwórz w &Firefoksie
+Description[pt]=Abrir com o &Firefox
+Description[pt_BR]=Abrir com o &Firefox
+Description[ro]=Deschide cu &Firefox
+Description[ru]=Открыть в &Firefox
+Description[se]=Raba &Firefox:ain
+Description[sk]=Otvoriť pomocou &Firefox
+Description[sl]=Odpri s &Firefox
+Description[sr]=Отвори помоћу &Firefox-а
+Description[sr@Latn]=Otvori pomoću &Firefox-a
+Description[sv]=Öppna med &Firefox
+Description[te]=(&F) మొజిల్లా తొ తెరువు
+Description[tg]=Кушодан бо &Firefox
+Description[th]=เปิดด้วยไ&ฟร์ฟอกซ์
+Description[tr]=&Firefox ile Aç
+Description[uk]=Відкрити з &Firefox
+Description[uz]=Firefox b&ilan ochish
+Description[uz@cyrillic]=Firefox б&илан очиш
+Description[vi]=Mở bằng &Firefox
+Description[wa]=Drovi avou &Firefox
+Description[zh_CN]=用 &Firefox 打开
+Description[zh_TW]=使用 &Firefox 開啟
+Enabled=true
+
+[Action_1/Command_4]
+Commandline=kmail --attach %s
+Description=Send &Page
+Description[af]=Stuur Bladsy
+Description[ar]=ارسال ال&صفحة
+Description[az]=&Səhifə Göndər
+Description[be]=Даслаць &старонку
+Description[bg]=И&зпращане на страница
+Description[bn]=পৃষ্ঠা পাঠা&ও
+Description[br]=Kas ar &pajenn
+Description[bs]=Pošalji &Stranicu
+Description[ca]=Envia la &pàgina
+Description[cs]=&Poslat stránku
+Description[csb]=Wëslij &starnã
+Description[cy]=Anfon &Tudalen
+Description[da]=Send &side
+Description[de]=&Seite senden
+Description[el]=Αποστολή &σελίδας
+Description[eo]=Sendi &paĝon
+Description[es]=Enviar &página
+Description[et]=Lehek&ülje saatmine
+Description[eu]=Bidali &Orria
+Description[fa]=ارسال &صفحه‌
+Description[fi]=Lähetä &Sivu
+Description[fr]=Envoyer la &page
+Description[fy]=&Side stjoere
+Description[ga]=Seol &Leathanach
+Description[gl]=Enviar &Páxina
+Description[he]=&שלח עמוד
+Description[hi]=पृष्ठ भेजें (&P)
+Description[hr]=Pošalji &stranicu
+Description[hu]=A &weboldal elküldése
+Description[is]=Senda &síðu
+Description[it]=Invia la &pagina
+Description[ja]=ページを送信(&P)
+Description[ka]=&გვერდის გაგზავნა
+Description[kk]=&Парақты жіберу
+Description[km]=ផ្ញើ​ទំព័រ
+Description[ko]=페이지 보내기(&P)
+Description[lo]=ສົ່ງຫນ້າ &Page
+Description[lt]=Siųsti &puslapį
+Description[lv]=Sūtīt La&pu
+Description[mk]=Испрати &страница
+Description[mn]=&Хуудас илгээх
+Description[ms]=Hantar &Halaman
+Description[mt]=Ibgħat &Paġna
+Description[nb]=Send &side
+Description[nds]=Sie&t schicken
+Description[ne]=पृष्ठ पठाउनुहोस्
+Description[nl]=&Pagina verzenden
+Description[nn]=Send &side
+Description[nso]=Romela &Letlakala
+Description[pa]=ਸਫ਼ਾ ਭੇਜੋ(&P)
+Description[pl]=Wyślij &stronę
+Description[pt]=Enviar a &Página
+Description[pt_BR]=Enviar &Página
+Description[ro]=Trimite &pagina
+Description[ru]=Отправить &страницу
+Description[rw]=Kohereza Ipaji
+Description[se]=Sádde &siiddu
+Description[sk]=Poslať &stránku
+Description[sl]=Pošlji &stran
+Description[sr]=Пошаљи &страну
+Description[sr@Latn]=Pošalji &stranu
+Description[sv]=Ski&cka sida
+Description[ta]=பக்கத்தை அனுப்பு
+Description[te]=(&P) పుట ను పంపించు
+Description[tg]=Фиристодани &саҳифа
+Description[th]=ส่ง&หน้าเว็บเพจ
+Description[tr]=&Sayfayı Gönder
+Description[tt]=&Bit Künderü
+Description[uk]=Відіслати &сторінку
+Description[uz]=&Betni joʻnatish
+Description[uz@cyrillic]=&Бетни жўнатиш
+Description[ven]=Rumele &Siatari
+Description[vi]=Gửi &Trang
+Description[wa]=Evoyî &pådje
+Description[xh]=Thumela &Iphepha
+Description[zh_CN]=发送页面(&P)
+Description[zh_TW]=送出 &Page
+Description[zu]=Thumela &Ikhasi
+Enabled=true
+
+[Action_2]
+Description=Mail-URL
+Description[ar]=رابط بريد
+Description[az]=Məktub-URL
+Description[be]=Спасылка на паштовы адрас
+Description[bg]=Изпращане на писмо с адрес
+Description[bn]=মেইল ইউ-আর-এল
+Description[br]=URL postel
+Description[ca]=URL de correu
+Description[cs]=Poštovní URL
+Description[csb]=Wëslij pòcztą adresã (URL)
+Description[cy]=URL Ebost
+Description[da]=E-mail-URL
+Description[de]=Mail-Adresse (URL)
+Description[eo]=Retpoŝto-URL
+Description[es]=URL de correo
+Description[et]=E-posti aadressi URL
+Description[eu]=Posta URLa
+Description[fa]=نشانی ‌وب نامه
+Description[fi]=Postita-URL
+Description[fr]=URL de courrier électronique
+Description[fy]=E-post URL-adres
+Description[gl]=Enviar URL por correo-e
+Description[he]=כתובת דואר
+Description[hi]=डाक-यूआरएल
+Description[hr]=URL pošalji e-poštom
+Description[hu]=Az URL elküldése levélben
+Description[is]=Póstsenda slóð
+Description[it]=URL di posta elettronica
+Description[km]=URL សំបុត្រ
+Description[lo]=‍ຕຳແຫນ່ງ URLຂອງເມລ
+Description[lt]=Siųsti URL paštu
+Description[lv]=Mailot URL
+Description[mk]=Испрати URL по е-пошта
+Description[mn]=Захиа-Хаяг (URL)
+Description[ms]=URL Mel
+Description[mt]=Url ta' imejl
+Description[nb]=E-postadresse
+Description[nds]=Nettpostadress
+Description[ne]=मेल-यूआरएल
+Description[nl]=URL-adres e-mailen
+Description[nn]=E-postadresse
+Description[nso]=Poso ya URL
+Description[pa]=ਪੱਤਰ-URL
+Description[pl]=Wyślij pocztą adres (URL)
+Description[pt]=URL de E-mail
+Description[pt_BR]=Enviar a URL por e-mail
+Description[ro]=URL email
+Description[rw]=URL-Ubutumwa
+Description[se]=E-boastačujuhus
+Description[sl]=Pošlji URL po pošti
+Description[sr]=Пошаљи URL eлектронском поштом
+Description[sr@Latn]=Pošalji URL elektronskom poštom
+Description[sv]=Webbadress för e-post
+Description[ta]=அஞ்சல்-வலைப்பின்னல்
+Description[te]=యూ ఆర్ ఎల్ ను మెయిల్ చెయ్యి
+Description[tg]=Адреси почтоӣ
+Description[th]=ตำแหน่ง URL ของจดหมาย
+Description[tr]=URL postala
+Description[tt]=Xat-URL
+Description[uk]=URL пошти
+Description[uz]=Xat-URL
+Description[uz@cyrillic]=Хат-URL
+Description[ven]=Meili-URL
+Description[vi]=Gửi thư-URL
+Description[wa]=Hårdêye emile
+Description[xh]=Iposi-URL
+Description[zh_CN]=邮寄 URL
+Description[zh_TW]=郵寄 URL
+Description[zu]=I-URl yeposi
+Number of commands=2
+Regexp=^mailto:.
+
+[Action_2/Command_0]
+Commandline=kmail --composer `echo %s | sed 's/mailto://g'`
+Description=Launch &Kmail
+Description[af]=Lanseer Kpos
+Description[ar]=تشغيل &Kmail
+Description[az]=&Kmail-i Başlat
+Description[be]=Запусціць &KMail
+Description[bg]=Ст&артиране на Kmail
+Description[bn]=কে-মেই&ল চালাও
+Description[br]=Lañsañ &Kmail
+Description[bs]=Pokreni &Kmail
+Description[ca]=Engega el &Kmail
+Description[cs]=Spustit &Kmail
+Description[csb]=Zrëszë &KMail
+Description[cy]=Cychwyn &KMail
+Description[da]=Start &KMail
+Description[de]=&Kmail starten
+Description[el]=Εκκίνηση &Kmail
+Description[en_GB]=Launch &KMail
+Description[eo]=Lanĉi &Poŝtilon
+Description[es]=Iniciar &Kmail
+Description[et]=&Kmaili käivitamine
+Description[eu]=Abiarazi &Kmail
+Description[fa]=راه‌اندازی&Kmail‌
+Description[fi]=Käynistä &Kmail
+Description[fr]=Démarrer K&Mail
+Description[fy]=&Kmail útfiere
+Description[ga]=Tosaigh &Kmail
+Description[gl]=Executar &Kmail
+Description[he]=הפעל את &KMail
+Description[hi]=के-मेल चालू करें (&K)
+Description[hr]=Pokreni &Kmail poštu
+Description[hu]=A &KMail elindítása
+Description[is]=Ræsa &Kmail
+Description[it]=Avvia &KMail
+Description[ja]=Kmail を起動(&K)
+Description[ka]=&Kmail-ის გაშვება
+Description[kk]=&Kmail-ды жегу
+Description[km]=បើក &Kmail
+Description[ko]=KMail 실행(&K)
+Description[lo]=ຮງກທຳງນ &Kmail
+Description[lt]=Paleisti &Kmail
+Description[lv]=Palaist &Kpastu
+Description[mk]=Стартувај &Kmail
+Description[mn]=&Kmail эхлүүлэх
+Description[ms]=Lancar &Kmail
+Description[mt]=Ħaddem &KMail
+Description[nb]=Start &Kmail
+Description[nds]=&KMail starten
+Description[ne]=के मेल सुरुआत गर्नुहोस्
+Description[nl]=&Kmail opstarten
+Description[nn]=Start &KMail
+Description[nso]=Ngwadisaleswa &Kposo
+Description[pa]=ਕੇ-ਮੇਲ ਜਾਰੀ(&K)
+Description[pl]=Uruchom KM&ail
+Description[pt]=Lançar o &Kmail
+Description[pt_BR]=Abrir &Kmail
+Description[ro]=Execută &Kmail
+Description[ru]=Запуск &Kmail
+Description[rw]=Gutangiza K-ubutumwa
+Description[se]=Álggat K&Mail
+Description[sk]=Spustiť &Kmail
+Description[sl]=Zaženi &Kmail
+Description[sr]=Покрени &Kmail
+Description[sr@Latn]=Pokreni &Kmail
+Description[sv]=Starta &Kmail
+Description[ta]=Kஅஞ்சலை இறக்கு
+Description[tg]=Ба кор даровардани &Kmail
+Description[th]=เรียกทำงานโปรแกรมรับส่งเมล์ &K
+Description[tr]=KMail Ç&alıştır
+Description[tt]=&Kmail Cibärü
+Description[uk]=Запустити &Kmail
+Description[uz]=&Kmail'ni ishga tushirish
+Description[uz@cyrillic]=&Kmail'ни ишга тушириш
+Description[ven]=Thoma &Kmeili
+Description[vi]=Khởi động &Kmail
+Description[wa]=Enonder &Kmail
+Description[zh_CN]=调用 &Kmail
+Description[zh_TW]=啟動 &Kmail
+Description[zu]=Qalisa i-&Kmail
+Enabled=true
+
+[Action_2/Command_1]
+Commandline=konsole -e mutt `echo %s | sed 's/mailto://g'`
+Description=Launch &mutt
+Description[af]=Lanseer mutt
+Description[ar]=تشغيل &Mutt
+Description[az]=&mutt-u Başlat
+Description[be]=Запусціць &mutt
+Description[bg]=Ста&ртиране на mutt
+Description[bn]=&mutt চালাও
+Description[br]=Lañsañ &mutt
+Description[bs]=Pokreni &mutt
+Description[ca]=Engega el &mutt
+Description[cs]=Spustit &mutt
+Description[csb]=Zrëszë &mutt
+Description[cy]=Cychwyn &mutt
+Description[da]=Start &mutt
+Description[de]=&Mutt starten
+Description[el]=Εκκίνηση &mutt
+Description[en_GB]=Launch &Mutt
+Description[eo]=Lanĉi poŝtilon "&Mutt"
+Description[es]=Iniciar &mutt
+Description[et]=&Mutt'i käivitamine
+Description[eu]=Abiarazi &mutt
+Description[fa]=راه‌اندازی &mutt‌
+Description[fi]=Käynistä &Mutt
+Description[fr]=Démarrer &Mutt
+Description[fy]=&Mutt útfiere
+Description[ga]=Tosaigh &mutt
+Description[gl]=Executar &mutt
+Description[he]=הפעל את &mutt
+Description[hi]=मट चालू करें (&m)
+Description[hr]=Pokreni &mutt
+Description[hu]=A &Mutt elindítása
+Description[is]=Ræsa &mutt
+Description[it]=Avvia &mutt
+Description[ja]=mutt を起動(&M)
+Description[ka]=&mutt-ის გაშვება
+Description[kk]=&mutt-ті жегу
+Description[km]=បើក &mutt
+Description[ko]=mutt 실행(&M)
+Description[lo]=ຮງກທຳງານ &mutt
+Description[lt]=Paleisti &mutt
+Description[lv]=Palaist &mutt
+Description[mk]=Стартувај &mutt
+Description[mn]=&Mutt эхлүүлэх
+Description[ms]=Lancarkan &mutt
+Description[mt]=Ħaddem &mutt
+Description[nb]=Start &mutt
+Description[nds]=&mutt starten
+Description[ne]=मट सुरुआत गर्नुहोस्
+Description[nl]=&Mutt opstarten
+Description[nn]=Start &mutt
+Description[nso]=Ngwadisaleswa &mutt
+Description[pa]=ਮੂਟ ਜਾਰੀ(&m)
+Description[pl]=Uruchom &mutt
+Description[pt]=Lançar o &mutt
+Description[pt_BR]=Abrir &mutt
+Description[ro]=Execută &mutt
+Description[ru]=Запуск &mutt
+Description[rw]=Gutangiza mutt
+Description[se]=Álggat &mutt
+Description[sk]=Spustiť &mutt
+Description[sl]=Zaženi &mutt
+Description[sr]=Покрени &mutt
+Description[sr@Latn]=Pokreni &mutt
+Description[sv]=Starta &Mutt
+Description[ta]=muttஐ இறக்கு
+Description[tg]=Ба кор даровардани &mutt
+Description[th]=เรียกใช้งาน &mutt
+Description[tr]=&Mutt Çalıştır
+Description[tt]=&mutt Cibärü
+Description[uk]=Запустити &mutt
+Description[uz]= &Mutt'ni ishga tushirish
+Description[uz@cyrillic]= &Mutt'ни ишга тушириш
+Description[ven]=Thoma &mutt
+Description[vi]=Khởi động &mutt
+Description[wa]=Enonder &mutt
+Description[zh_CN]=调用 &mutt
+Description[zh_TW]=啟動 &mutt
+Description[zu]=Qalisa i-&mutt
+Enabled=true
+
+[Action_3]
+Description=Text File
+Description[af]=Teks Lêer
+Description[ar]=ملف نصي
+Description[az]=Mətn Faylı
+Description[be]=Тэкставы файл
+Description[bg]=Текстов файл
+Description[bn]=টেক্সট ফাইল
+Description[br]=Restr testenn
+Description[bs]=Tekst datoteka
+Description[ca]=Fitxer de text
+Description[cs]=Textový soubor
+Description[csb]=Tekstowi lopk
+Description[cy]=Ffeil Testun
+Description[da]=Tekstfil
+Description[de]=Textdatei
+Description[el]=Αρχείο κειμένου
+Description[eo]=Tekstdosiero
+Description[es]=Archivo de texto
+Description[et]=Tekstifail
+Description[eu]=Testu fitxategia
+Description[fa]=پروندۀ‌ متنی
+Description[fi]=Tekstitiedosto
+Description[fo]=Tekstfíla
+Description[fr]=Fichier texte
+Description[fy]=Teksttriem
+Description[ga]=Téacschomhad
+Description[gl]=Ficheiro de Texto
+Description[he]=קובץ טקסט
+Description[hi]=पाठ फ़ाइल
+Description[hr]=Tekstualna datoteka
+Description[hu]=Szöveges fájl
+Description[is]=Textaskrá
+Description[it]=File di testo
+Description[ja]=テキストファイル
+Description[ka]=ტექსტური ფაილი
+Description[kk]=Мәтін файлы
+Description[km]=ឯកសារ​អត្ថបទ
+Description[lo]=ແຟ້ມາຂໍ້ຄວາມ
+Description[lt]=Tekstinė byla
+Description[lv]=Teksta Fails
+Description[mk]=Текстуална датотека
+Description[mn]=Текст файл
+Description[ms]=Fail Teks
+Description[mt]=Fajl ta' test
+Description[nb]=Tekstfil
+Description[nds]=Textdatei
+Description[ne]=पाठ फाइल
+Description[nl]=Tekstbestand
+Description[nn]=Tekstfil
+Description[nso]=Faele ya Sengwalwana
+Description[pa]=ਪਾਠ ਫਾਇਲਾਂ
+Description[pl]=Plik tekstowy
+Description[pt]=Ficheiro de Texto
+Description[pt_BR]=Arquivo Texto
+Description[ro]=Fișier Text
+Description[ru]=Текстовый файл
+Description[rw]=Idosiye Mwandiko
+Description[se]=Teakstafiila
+Description[sk]=Textový súbor
+Description[sl]=Besedilna datoteka
+Description[sr]=Текстуални фајл
+Description[sr@Latn]=Tekstualni fajl
+Description[sv]=Textfil
+Description[ta]=உரை கோப்பு
+Description[te]=వచన దస్త్రం
+Description[tg]=Файли матнӣ
+Description[th]=แฟ้มข้อความ
+Description[tr]=Metin Dosyası
+Description[tt]=Mäten Bireme
+Description[uk]=Текстовий файл
+Description[uz]=Matn fayli
+Description[uz@cyrillic]=Матн файли
+Description[ven]=Manwalwa a faela
+Description[vi]=Tập tin Văn bản
+Description[wa]=Fitchî tecse
+Description[xh]=Ifayile Yombhalo
+Description[zh_CN]=文本文件
+Description[zh_TW]=文字檔案
+Description[zu]=Ifayela Lombhalo
+Number of commands=2
+Regexp=^\\/.+\\.txt$
+
+[Action_3/Command_0]
+Commandline=kedit %s
+Description=Launch K&Edit
+Description[af]=Lanseer Kredigeer
+Description[ar]=تشغيل K&Edit
+Description[az]=K&Edit-i Başlat
+Description[be]=Запусціць K&Edit
+Description[bg]=&Стартиране на KEdit
+Description[bn]=কে-&এডিট চালাও
+Description[br]=Lañsañ K&Edit
+Description[bs]=Pokreni K&Edit
+Description[ca]=Engega el K&Edit
+Description[cs]=Spustit K&Edit
+Description[csb]=Zrëszë editorã K&Edit
+Description[cy]=Cychwyn K&Edit
+Description[da]=Start K&Edit
+Description[de]=K&Edit starten
+Description[el]=Εκκίνηση K&Edit
+Description[eo]=Lanĉi &Kodredaktilon
+Description[es]=Iniciar K&Edit
+Description[et]=K&Editi käivitamine
+Description[eu]=Abiarazi K&Edit
+Description[fa]=راه‌اندازیK&Edit‌
+Description[fi]=Käynistä K&Edit
+Description[fr]=Démarrer K&Edit
+Description[fy]=Mei K&Edit begjinne
+Description[ga]=Tosaigh K&Edit
+Description[gl]=Executar K&Edit
+Description[he]=הפעל את K&Edit
+Description[hi]=के-एडिट चालू करें (&E)
+Description[hr]=Pokreni K&Edit uređivač
+Description[hu]=A K&Edit elindítása
+Description[is]=Ræsa K&Edit
+Description[it]=Avvia K&Edit
+Description[ja]=KEdit を起動(&E)
+Description[ka]=K&Edit-ის გაშვება
+Description[kk]=K&Edit-ты жегу
+Description[km]=បើក K&Edit
+Description[lo]=ຮງກທຳງານ K&Edit
+Description[lt]=Paleisti K&Edit
+Description[lv]=Palaist K&Edit
+Description[mk]=Стартувај K&Edit
+Description[mn]=K&Edit эхлүүлэх
+Description[ms]=Lancar K&Edit
+Description[mt]=Ħaddem K&Edit
+Description[nb]=Start K&Edit
+Description[nds]=K&Edit starten
+Description[ne]=के सम्पादन सुरुआत गर्नुहोस्
+Description[nl]=K&Edit opstarten
+Description[nn]=Start K&Edit
+Description[nso]=Ngwadisaleswa K&Phetoso
+Description[pa]=ਕੇ-ਸੰਪਾਦਕ ਸ਼ੁਰੂ(&E)
+Description[pl]=Uruchom &Edytor
+Description[pt]=Lançar o K&Edit
+Description[pt_BR]=Abrir &Kedit
+Description[ro]=Execută K&Edit
+Description[ru]=Запуск K&Edit
+Description[rw]=Gutangiza K-Guhindura
+Description[se]=Álggat K&Edit
+Description[sk]=Spustiť K&Edit
+Description[sl]=Zaženi K&Edit
+Description[sr]=Покрени K&Edit
+Description[sr@Latn]=Pokreni K&Edit
+Description[sv]=Starta K&edit
+Description[ta]=K&Editஐ இறக்கு
+Description[tg]=Ба кор даровардани K&Edit
+Description[th]=เรียกใช้งาน ม&Edit K
+Description[tr]=K&edit Çalıştır
+Description[tt]=K&Edit Cibärü
+Description[uk]=Запустити K&Edit
+Description[uz]=K&Edit'ni ishga tushirish
+Description[uz@cyrillic]=K&Edit'ни ишга тушириш
+Description[ven]=Thoma K&Sengulusa
+Description[vi]=Khởi động K&Edit
+Description[wa]=Enonder K&Edit
+Description[zh_CN]=调用 K&Edit
+Description[zh_TW]=啟動 K&Edit
+Description[zu]=Qalisa i-K&Edit
+Enabled=true
+
+[Action_3/Command_1]
+Commandline=kwrite %s
+Description=Launch K&Write
+Description[af]=Lanseer Kskryf
+Description[ar]=تشغيل K&Write
+Description[az]=K&Write-ı Başlat
+Description[be]=Запусціць K&Write
+Description[bg]=С&тартиране на KWrite
+Description[bn]=কে-রাই&ট চালাও
+Description[br]=Lañsañ K&Write
+Description[bs]=Pokreni K&Write
+Description[ca]=Engega el K&Write
+Description[cs]=Spustit K&Write
+Description[csb]=Zrëszë editorã K&Write
+Description[cy]=Cychwyn K&Write
+Description[da]=Start K&Write
+Description[de]=K&Write starten
+Description[el]=Εκκίνηση K&Write
+Description[eo]=Lanĉas &simplan kodredaktilon
+Description[es]=Inicar K&Write
+Description[et]=K&Write'i käivitamine
+Description[eu]=Abiarazi K&Write
+Description[fa]=راه‌اندازی K&Write‌
+Description[fi]=Käynistä K&Write
+Description[fr]=Démarrer K&Write
+Description[fy]=K&Write útfiere
+Description[ga]=Tosaigh K&Write
+Description[gl]=Executar K&Write
+Description[he]=הפעל את K&Write
+Description[hi]=के-राइट चालू करें (&W)
+Description[hr]=Pokreni K&Write uređivač
+Description[hu]=A K&Write elindítása
+Description[is]=Ræsa K&Write
+Description[it]=Avvia K&Write
+Description[ja]=KWrite を起動(&W)
+Description[ka]=K&Write-ის გაშვება
+Description[kk]=K&Write-ты жегу
+Description[km]=បើក KWrite
+Description[ko]=KWrite 실행(&W)
+Description[lo]=ຮງດທຳງານ K&Write
+Description[lt]=Paleisti K&Write
+Description[lv]=Palaist K&Write
+Description[mk]=Стартувај K&Write
+Description[mn]=K&Write эхлүүлэх
+Description[ms]=Lancar K&Write
+Description[mt]=Ħaddem K&Write
+Description[nb]=Start K&Write
+Description[nds]=K&Write starten
+Description[ne]=के राइट सुरुआत गर्नुहोस्
+Description[nl]=K&Write opstarten
+Description[nn]=Start K&Write
+Description[nso]=Ngwadisaleswa K&Ngwala
+Description[pa]=ਕੇ-ਲੇਖਕ ਸ਼ੁਰੂ(&W)
+Description[pl]=Uruchom &Zaawansowany edytor
+Description[pt]=Lançar o K&Write
+Description[pt_BR]=Abrir KW&rite
+Description[ro]=Execută K&Write
+Description[ru]=Запуск K&Write
+Description[rw]=Gutangiza K-Kwandika
+Description[se]=Álggat &KWrite
+Description[sk]=Spustiť K&Write
+Description[sl]=Zaženi K&Write
+Description[sr]=Покрени K&Write
+Description[sr@Latn]=Pokreni K&Write
+Description[sv]=Starta K&write
+Description[ta]= K&Writeஐ இறக்கு
+Description[tg]=Ба кор даровардани K&Write
+Description[th]=เรียกใช้งาน K&Write
+Description[tr]=K&Write Çalıştır
+Description[tt]=K&Write Cibärü
+Description[uk]=Запустити K&Write
+Description[uz]=K&Write'ni ishga tushirish
+Description[uz@cyrillic]=K&Write'ни ишга тушириш
+Description[ven]=Thoma K&Nwala
+Description[vi]=Khởi động K&Write
+Description[wa]=Enonder K&Write
+Description[zh_CN]=调用 K&Write
+Description[zh_TW]=啟動 K&Write
+Description[zu]=Qalisa K&Write
+Enabled=true
+
+[Action_4]
+Description=Local file URL
+Description[af]=Plaaslike lêer Url
+Description[ar]=عنوان ملف محلي
+Description[az]=Yerli fayl URL-si
+Description[be]=URL мясцовага файла
+Description[bg]=Локален файл/адрес
+Description[bn]=স্থানীয় ফাইল ইউ-আর-এল
+Description[br]=URL ur restr lec'hel
+Description[bs]=URL lokalne datoteke
+Description[ca]=URL d'un fitxer local
+Description[cs]=Místní URL
+Description[csb]=Lokalnô adresa lopkù
+Description[cy]=URL ffeil lleol
+Description[da]=Lokal fil-URL
+Description[de]=Adresse einer lokalen Datei (URL)
+Description[el]=URL τοπικού αρχείου
+Description[eo]=URL al loka dosiero
+Description[es]=URL de archivo local
+Description[et]=Kohaliku faili URL
+Description[eu]=Fitxategi lokalaren URLa
+Description[fa]=نشانی وب پروندۀ محلی
+Description[fi]=Paikallisen tiedoston URL
+Description[fr]=URL locale
+Description[fy]=Lokale triem-URL-adres
+Description[gl]=URL do ficheiro local
+Description[he]=כתובת של קובץ מקומי
+Description[hi]=स्थानीय फ़ाइल यूआरएल
+Description[hr]=URL lokalne datoteke
+Description[hu]=helyi fájl URL-je
+Description[is]=Staðbundin slóð
+Description[it]=URL locale
+Description[ja]=ローカルファイル URL
+Description[ka]=ბმული ლოკალურ ფაილზე
+Description[kk]=Жергілікті файлдың URL
+Description[km]=URL ឯកសារ​មូលដ្ឋាន
+Description[ko]=로컬 파일 URL
+Description[lo]=ຕຳແຫນ່ງ URL ແຟ້ມພາບໃນ
+Description[lt]=Vietinės bylos URL
+Description[lv]=Lokāla faila URL
+Description[mk]=URL на локалната датотека
+Description[mn]=Дотоод файлын хаяг(URL)
+Description[ms]=URL fail setempat
+Description[mt]=URL ta' fajl lokali
+Description[nb]=Lokal filadresse
+Description[nds]=URL vun en lokale Datei
+Description[ne]=स्थानीय फाइल यूआरएल
+Description[nl]=Lokaal bestands-URL-adres
+Description[nn]=Lokal filadresse
+Description[nso]=URL ya Faele ya Selegae
+Description[pa]=ਸਥਾਨਕ ਫਾਇਲ URL
+Description[pl]=Adres lokalnego pliku
+Description[pt]=URL de ficheiro local
+Description[pt_BR]=URL de arquivo local
+Description[ro]=URL de fișier local
+Description[ru]=Ссылка на локальный файл
+Description[rw]=URL y'idosiye ya hafi
+Description[se]=Báikkalaš fiilačujuhus
+Description[sk]=URL lokálneho súboru
+Description[sl]=URL krajevne datoteke
+Description[sr]=URL локалног фајла
+Description[sr@Latn]=URL lokalnog fajla
+Description[sv]=Webbadress för lokal fil
+Description[ta]=உள் கோப்பு வலைப்பின்னல்
+Description[te]=స్థానిక దస్త్ర యూ ఆర్ ఎల్
+Description[tg]=Нишони файли маҳлӣ
+Description[th]=ตำแหน่งแฟ้มภายใน
+Description[tr]=Yerel dosya URL'i
+Description[tt]=Cirle birem URLı
+Description[uk]=URL локального файла
+Description[uz]=Lokal faylining URL'i
+Description[uz@cyrillic]=Локал файлининг URL'и
+Description[ven]=URL ya faela ya henefhano
+Description[vi]=URL Tập tin Địa phương
+Description[wa]=Hårdêye viè on fitchî locå
+Description[xh]=Ifayile yobulali ye URL
+Description[zh_CN]=本地文件 URL
+Description[zh_TW]=本地檔案 URL
+Description[zu]=I-URL yefayela eseduze
+Number of commands=2
+Regexp=^file:.
+
+[Action_4/Command_0]
+Commandline=kmail --body %s
+Description=Send &URL
+Description[af]=Stuur URL
+Description[ar]=ارسال &رابط
+Description[az]=&URL Göndər
+Description[be]=Даслаць &URL
+Description[bg]=&Изпращане на адрес
+Description[bn]=ই&উ-আর-এল পাঠাও
+Description[br]=Kas an &URL
+Description[bs]=Pošalji &URL
+Description[ca]=Envia l'&URL
+Description[cs]=Poslat &URL
+Description[csb]=Wëslij adresã (&URL)
+Description[cy]=Anfon &URL
+Description[de]=&URL senden
+Description[el]=Αποστολή &URL
+Description[eo]=Sendi &URLon
+Description[es]=Enviar &URL
+Description[et]=&URL-i saatmine
+Description[eu]=Bidali &URLa
+Description[fa]=ارسال &نشانی ‌وب‌
+Description[fi]=Lähetä &URL
+Description[fr]=Envoyer l'&URL
+Description[fy]=&URL-adres stjoere
+Description[ga]=Seol &URL
+Description[gl]=Enviar &URL
+Description[he]=שלח &כתובת
+Description[hi]= यूआरएल भेजें (&U)
+Description[hr]=Pošalji &URL
+Description[hu]=Az &URL elküldése
+Description[is]=Senda &slóð
+Description[it]=Invia l'&URL
+Description[ja]=URL を送信(&U)
+Description[ka]=&URL-ს გაგზავნა
+Description[kk]=&URL-ды жіберу
+Description[km]=ផ្ញើ &URL
+Description[lo]=ສັ່ງຕຳແຫນ່ງ &URL
+Description[lt]=Siųsti &URL
+Description[lv]=Sūtīt &URL
+Description[mk]=Испрати &URL
+Description[mn]=&URL илгээх
+Description[ms]=Hantar &URL
+Description[mt]=Ibgħat &URL
+Description[nb]=Send &Nettadressen
+Description[nds]=&URL schicken
+Description[ne]=यूआरएल पठाउनुहोस्
+Description[nl]=&URL-adres verzenden
+Description[nso]=Romela &URL
+Description[pa]=&URL ਭੇਜੋ
+Description[pl]=Wyślij adres (&URL)
+Description[pt]=Enviar o &URL
+Description[pt_BR]=Enviar &URL
+Description[ro]=Trimite &URL-ul
+Description[ru]=Отправить &URL
+Description[rw]=Kohereza URL
+Description[se]=Sádde &URL:a
+Description[sk]=Poslať &URL
+Description[sl]=Pošlji &URL
+Description[sr]=Пошаљи &URL
+Description[sr@Latn]=Pošalji &URL
+Description[sv]=Skicka &webbadress
+Description[ta]=வலைப்பின்னலை அனுப்பு
+Description[te]=(&U) యూ ఆర్ ఎల్ పంపించు
+Description[tg]=Фиристодани &URL
+Description[th]=ส่งตำแหน่ง &URL
+Description[tr]=&URL Gönder
+Description[tt]=&URL Künderü
+Description[uk]=Відіслати &URL
+Description[uz]=URL'ni &joʻnatish
+Description[uz@cyrillic]=URL'ни &жўнатиш
+Description[ven]=Rumele &URL
+Description[vi]=Gửi&URL
+Description[wa]=Evoyî &hårdêye
+Description[xh]=Thumela i &URL
+Description[zh_CN]=发送 &URL
+Description[zh_TW]=送出 &URL
+Description[zu]=Thumela i-&URL
+Enabled=true
+
+[Action_4/Command_1]
+Commandline=kmail --attach %s
+Description=Send &File
+Description[af]=Stuur Lêer
+Description[ar]=ارسال م&لف
+Description[az]=&Fayl Göndər
+Description[be]=Даслаць &файл
+Description[bg]=Изпращане на &файл
+Description[bn]=ফাই&ল পাঠাও
+Description[br]=Kas ar &restr
+Description[bs]=Pošalji &Datoteku
+Description[ca]=Envia &fitxer
+Description[cs]=Posl&at soubor
+Description[csb]=Wëslij &lopk
+Description[cy]=Anfon &Ffeil
+Description[da]=Send &fil
+Description[de]=&Datei senden
+Description[el]=Αποστολή &αρχείου
+Description[eo]=Sendi &dosieron
+Description[es]=Enviar &archivo
+Description[et]=&Faili saatmine
+Description[eu]=Bidali &Fitxategia
+Description[fa]=ارسال &پرونده‌
+Description[fi]=Lähetä &tiedosto
+Description[fr]=Envoyer le &fichier
+Description[fy]=&Triem stjoere
+Description[ga]=Seol &Comhad
+Description[gl]=Enviar &Ficheiro
+Description[he]=שלח &קובץ
+Description[hi]=फ़ाइल भेजें (&F)
+Description[hr]=Pošalji &datoteku
+Description[hu]=&Fájl elküldése
+Description[is]=Senda &skrá
+Description[it]=Invia il &file
+Description[ja]=ファイルを送信(&F)
+Description[ka]=&ფაილის გაგზავნა
+Description[kk]=&Файлды жіберу
+Description[km]=ផ្ញើ​ឯកសារ
+Description[ko]=파일 보내기(&F)
+Description[lo]=ສົ່ງແຟ້ມ &File
+Description[lt]=Siųsti &bylą
+Description[lv]=Sūtīt &Failu
+Description[mk]=Испрати &датотека
+Description[mn]=&Файл илгээх
+Description[ms]=Hantar &Fail
+Description[mt]=Ibgħat &fajl
+Description[nb]=Send &Fil
+Description[nds]=Datei &schicken
+Description[ne]=फाइल पठाउनुहोस्
+Description[nl]=&Bestand verzenden
+Description[nn]=Send &fil
+Description[nso]=Romela &Faele
+Description[pa]=ਫਾਇਲ ਭੇਜੋ(&F)
+Description[pl]=Wyślij &plik
+Description[pt]=Enviar o &Ficheiro
+Description[pt_BR]=Enviar &Arquivo
+Description[ro]=Trimite &fișierul
+Description[ru]=Отправить &файл
+Description[rw]=Kohereza Idosiye
+Description[se]=Sádde &fiilla
+Description[sk]=Poslať &súbor
+Description[sl]=Pošlji &datoteko
+Description[sr]=Пошаљи &фајл
+Description[sr@Latn]=Pošalji &fajl
+Description[sv]=Skicka &fil
+Description[ta]=கோப்பை அனுப்பு
+Description[te]=(&F) దస్త్రాన్ని పంపించు
+Description[tg]=Фиристодани &файл
+Description[th]=ส่งแฟ้ม
+Description[tr]=Dosya &Gönder
+Description[tt]=Bir&em Cibärü
+Description[uk]=Відіслати &File
+Description[uz]=&Faylni joʻnatish
+Description[uz@cyrillic]=&Файлни жўнатиш
+Description[ven]=Rumela &Faela
+Description[vi]=Gửi &Tập tin
+Description[wa]=Evoyî &fitchî
+Description[xh]=Thumela &Ifayile
+Description[zh_CN]=发送文件(&F)
+Description[zh_TW]=送出檔案(&F)
+Description[zu]=Thumela &Ifayela
+Enabled=true
+
+[Action_5]
+Description=Gopher URL
+Description[af]=Gopher Url
+Description[ar]=رابط عنوان Gopher
+Description[az]=Qofher URL
+Description[bg]=Адрес Gopher
+Description[br]=URL Gopher
+Description[ca]=URL de Gopher
+Description[csb]=Adresa Gophera
+Description[cy]=URL Gopher
+Description[da]=Gopher-URL
+Description[de]=Gopher-Adresse (URL)
+Description[eo]=Gofer-URL
+Description[es]=URL de Gopher
+Description[et]=Gopher'i URL
+Description[eu]=Gopher URLa
+Description[fa]=نشانی وب گوفر
+Description[fr]=URL Gopher
+Description[fy]=Gopher URL-adres
+Description[ga]=URL Gopher
+Description[gl]=URL Gopher
+Description[he]=כתובת Gopher
+Description[hi]=गोफर यूआरएल
+Description[is]=Gopher slóð
+Description[it]=URL Gopher
+Description[ka]=Gopher ბმული
+Description[kk]=Gopher сілтемесі
+Description[lo]=ຕຳແຫນ່ງ URL ຂອງໂກເຟີ
+Description[mn]=Gopher-Хаяг (URL)
+Description[ms]=URL Gopher
+Description[mt]=URL ta' gopher
+Description[nb]=Gopheradresse
+Description[nds]=Gopher-URL
+Description[ne]=गोफर यूआरएल
+Description[nl]=Gopher URL-adres
+Description[nn]=Gopher-adresse
+Description[nso]=URL ya Gopher
+Description[pa]=ਗੋਫਰ URL
+Description[pl]=Adres Gophera
+Description[pt]=URL do Gopher
+Description[pt_BR]=URL para o Gopher
+Description[ro]=URL Gopher
+Description[ru]=Ссылка Gopher
+Description[se]=Gopher-čujuhus
+Description[sk]=URL pre Gopher
+Description[sl]=URL za gopher
+Description[sv]=Gopher-webbadress
+Description[ta]=கோப்பர் வலைப்பின்னல்
+Description[te]=గొఫర్ యూ ఆర్ ఎల్
+Description[tg]=Гуфери URL
+Description[th]=ตำแหน่ง URL ของโกเฟอร์
+Description[tr]=Gopher URL'i
+Description[tt]=Gopher URLı
+Description[uk]=URL від Gopher
+Description[vi]=Gửi Gopher URL
+Description[wa]=Hårdêye Gopher
+Description[zu]=I-URL ye-Gopher
+Number of commands=2
+Regexp=^gopher:.
+
+[Action_5/Command_0]
+Commandline=kmail --body %s
+Description=Send &URL
+Description[af]=Stuur URL
+Description[ar]=ارسال &رابط
+Description[az]=&URL Göndər
+Description[be]=Даслаць &URL
+Description[bg]=&Изпращане на адрес
+Description[bn]=ই&উ-আর-এল পাঠাও
+Description[br]=Kas an &URL
+Description[bs]=Pošalji &URL
+Description[ca]=Envia l'&URL
+Description[cs]=Poslat &URL
+Description[csb]=Wëslij adresã (&URL)
+Description[cy]=Anfon &URL
+Description[de]=&URL senden
+Description[el]=Αποστολή &URL
+Description[eo]=Sendi &URLon
+Description[es]=Enviar &URL
+Description[et]=&URL-i saatmine
+Description[eu]=Bidali &URLa
+Description[fa]=ارسال &نشانی ‌وب‌
+Description[fi]=Lähetä &URL
+Description[fr]=Envoyer l'&URL
+Description[fy]=&URL-adres stjoere
+Description[ga]=Seol &URL
+Description[gl]=Enviar &URL
+Description[he]=שלח &כתובת
+Description[hi]= यूआरएल भेजें (&U)
+Description[hr]=Pošalji &URL
+Description[hu]=Az &URL elküldése
+Description[is]=Senda &slóð
+Description[it]=Invia l'&URL
+Description[ja]=URL を送信(&U)
+Description[ka]=&URL-ს გაგზავნა
+Description[kk]=&URL-ды жіберу
+Description[km]=ផ្ញើ &URL
+Description[lo]=ສັ່ງຕຳແຫນ່ງ &URL
+Description[lt]=Siųsti &URL
+Description[lv]=Sūtīt &URL
+Description[mk]=Испрати &URL
+Description[mn]=&URL илгээх
+Description[ms]=Hantar &URL
+Description[mt]=Ibgħat &URL
+Description[nb]=Send &Nettadressen
+Description[nds]=&URL schicken
+Description[ne]=यूआरएल पठाउनुहोस्
+Description[nl]=&URL-adres verzenden
+Description[nso]=Romela &URL
+Description[pa]=&URL ਭੇਜੋ
+Description[pl]=Wyślij adres (&URL)
+Description[pt]=Enviar o &URL
+Description[pt_BR]=Enviar &URL
+Description[ro]=Trimite &URL-ul
+Description[ru]=Отправить &URL
+Description[rw]=Kohereza URL
+Description[se]=Sádde &URL:a
+Description[sk]=Poslať &URL
+Description[sl]=Pošlji &URL
+Description[sr]=Пошаљи &URL
+Description[sr@Latn]=Pošalji &URL
+Description[sv]=Skicka &webbadress
+Description[ta]=வலைப்பின்னலை அனுப்பு
+Description[te]=(&U) యూ ఆర్ ఎల్ పంపించు
+Description[tg]=Фиристодани &URL
+Description[th]=ส่งตำแหน่ง &URL
+Description[tr]=&URL Gönder
+Description[tt]=&URL Künderü
+Description[uk]=Відіслати &URL
+Description[uz]=URL'ni &joʻnatish
+Description[uz@cyrillic]=URL'ни &жўнатиш
+Description[ven]=Rumele &URL
+Description[vi]=Gửi&URL
+Description[wa]=Evoyî &hårdêye
+Description[xh]=Thumela i &URL
+Description[zh_CN]=发送 &URL
+Description[zh_TW]=送出 &URL
+Description[zu]=Thumela i-&URL
+Enabled=true
+
+[Action_5/Command_1]
+Commandline=kmail --attach %s
+Description=Send &File
+Description[af]=Stuur Lêer
+Description[ar]=ارسال م&لف
+Description[az]=&Fayl Göndər
+Description[be]=Даслаць &файл
+Description[bg]=Изпращане на &файл
+Description[bn]=ফাই&ল পাঠাও
+Description[br]=Kas ar &restr
+Description[bs]=Pošalji &Datoteku
+Description[ca]=Envia &fitxer
+Description[cs]=Posl&at soubor
+Description[csb]=Wëslij &lopk
+Description[cy]=Anfon &Ffeil
+Description[da]=Send &fil
+Description[de]=&Datei senden
+Description[el]=Αποστολή &αρχείου
+Description[eo]=Sendi &dosieron
+Description[es]=Enviar &archivo
+Description[et]=&Faili saatmine
+Description[eu]=Bidali &Fitxategia
+Description[fa]=ارسال &پرونده‌
+Description[fi]=Lähetä &tiedosto
+Description[fr]=Envoyer le &fichier
+Description[fy]=&Triem stjoere
+Description[ga]=Seol &Comhad
+Description[gl]=Enviar &Ficheiro
+Description[he]=שלח &קובץ
+Description[hi]=फ़ाइल भेजें (&F)
+Description[hr]=Pošalji &datoteku
+Description[hu]=&Fájl elküldése
+Description[is]=Senda &skrá
+Description[it]=Invia il &file
+Description[ja]=ファイルを送信(&F)
+Description[ka]=&ფაილის გაგზავნა
+Description[kk]=&Файлды жіберу
+Description[km]=ផ្ញើ​ឯកសារ
+Description[ko]=파일 보내기(&F)
+Description[lo]=ສົ່ງແຟ້ມ &File
+Description[lt]=Siųsti &bylą
+Description[lv]=Sūtīt &Failu
+Description[mk]=Испрати &датотека
+Description[mn]=&Файл илгээх
+Description[ms]=Hantar &Fail
+Description[mt]=Ibgħat &fajl
+Description[nb]=Send &Fil
+Description[nds]=Datei &schicken
+Description[ne]=फाइल पठाउनुहोस्
+Description[nl]=&Bestand verzenden
+Description[nn]=Send &fil
+Description[nso]=Romela &Faele
+Description[pa]=ਫਾਇਲ ਭੇਜੋ(&F)
+Description[pl]=Wyślij &plik
+Description[pt]=Enviar o &Ficheiro
+Description[pt_BR]=Enviar &Arquivo
+Description[ro]=Trimite &fișierul
+Description[ru]=Отправить &файл
+Description[rw]=Kohereza Idosiye
+Description[se]=Sádde &fiilla
+Description[sk]=Poslať &súbor
+Description[sl]=Pošlji &datoteko
+Description[sr]=Пошаљи &фајл
+Description[sr@Latn]=Pošalji &fajl
+Description[sv]=Skicka &fil
+Description[ta]=கோப்பை அனுப்பு
+Description[te]=(&F) దస్త్రాన్ని పంపించు
+Description[tg]=Фиристодани &файл
+Description[th]=ส่งแฟ้ม
+Description[tr]=Dosya &Gönder
+Description[tt]=Bir&em Cibärü
+Description[uk]=Відіслати &File
+Description[uz]=&Faylni joʻnatish
+Description[uz@cyrillic]=&Файлни жўнатиш
+Description[ven]=Rumela &Faela
+Description[vi]=Gửi &Tập tin
+Description[wa]=Evoyî &fitchî
+Description[xh]=Thumela &Ifayile
+Description[zh_CN]=发送文件(&F)
+Description[zh_TW]=送出檔案(&F)
+Description[zu]=Thumela &Ifayela
+Enabled=true
+
+[Action_6]
+Description=ftp URL
+Description[af]=ftp Url
+Description[ar]=رابط عنوان ftp ( URL )
+Description[be]=FTP URL
+Description[bg]=Адрес ftp
+Description[br]=URL FTP
+Description[ca]=URL de ftp
+Description[cs]=FTP URL
+Description[csb]=Adresa FTP
+Description[cy]=URL ftp
+Description[da]=ftp-URL
+Description[de]=FTP-Adresse (URL)
+Description[en_GB]=FTP URL
+Description[eo]=FTP-URL
+Description[es]=URL de ftp
+Description[et]=FTP URL
+Description[eu]=ftp URLa
+Description[fa]=نشانی وب قاپ
+Description[fr]=URL FTP
+Description[fy]=FTP URL-adres
+Description[ga]=URL ftp
+Description[gl]=URL ftp
+Description[he]=כתובת FTP
+Description[hi]=एफटीपी यूआरएल
+Description[hu]=FTP URL
+Description[is]=ftp slóð
+Description[it]=URL FTP
+Description[ka]=FTP ბმული
+Description[kk]=ftp сілтемесі
+Description[lo]=ຕຳແຫນ່ງ URL ຂອງ ftp
+Description[mn]=FTP-Хаяг (URL)
+Description[ms]=URL ftp
+Description[mt]=URL ta' ftp
+Description[nb]=Ftp-adressen
+Description[nds]=FTP-URL
+Description[ne]=ftp यूआरएल
+Description[nl]=FTP URL-adres
+Description[nn]=FTP-adresse
+Description[nso]=URL ya ftp
+Description[pl]=Adres FTP
+Description[pt]=URL de FTP
+Description[pt_BR]=URL para FTP
+Description[ro]=URL FTP
+Description[ru]=Ссылка FTP
+Description[rw]=URL ya ftp
+Description[se]=FTP-čujuhus
+Description[sk]=URL pre FTP
+Description[sl]=URL za FTP
+Description[sv]=FTP-webbadress
+Description[ta]=ftpவலைப்பின்னல்
+Description[te]=ఎఫ్ టి పి యూ ఆర్ ఎల్
+Description[th]=ตำแหน่ง URL ของ ftp
+Description[tr]=ftp URL'i
+Description[tt]=ftp URLı
+Description[uk]=URL ftp
+Description[vi]=Gửi ftp URL
+Description[wa]=Hårdêye FTP
+Description[zh_TW]=FTP URL
+Number of commands=5
+Regexp=^ftp://.
+
+[Action_6/Command_0]
+Commandline=kfmclient exec %s
+Description=Open with &Konqueror
+Description[af]=Maak oop met Konqueror
+Description[ar]=إفتح بــ &Konqueror
+Description[az]=&Konqueror ilə Aç
+Description[be]=Адкрыць у &Konqueror
+Description[bg]=&Отваряне с Konqueror
+Description[bn]=কনকরার দিয়ে খোলো (&ক)
+Description[br]=Digeriñ gant &Konqueror
+Description[bs]=Otvori u &Konqueroru
+Description[ca]=Obre amb el &Konqueror
+Description[cs]=Otevřít pomocí &Konqueroru
+Description[csb]=Òtemknij w &Konquerorze
+Description[cy]=Agor gyda &Konqueror
+Description[da]=Åbn med &Konqueror
+Description[de]=Mit &Konqueror öffnen
+Description[el]=Άνοιγμα με το &Konqueror
+Description[eo]=Malfermi per &Konkeranto
+Description[es]=Abrir con &Konqueror
+Description[et]=&Konqueroris avamine
+Description[eu]= Ireki &Konquerorrekin
+Description[fa]=باز کردن با&Konqueror‌
+Description[fi]=Avaa &Konquerorissa
+Description[fo]=Opna við &Konqueror
+Description[fr]=Ouvrir dans &Konqueror
+Description[fy]=Iepenje mei &Konqueror
+Description[ga]=Oscail le &Konqueror
+Description[gl]=Abrir con &Konqueror
+Description[he]=פתח באמצעות &Konqueror
+Description[hi]=कॉन्करर के साथ खोलें (&K)
+Description[hr]=Otvori pomoću &Konquerora
+Description[hu]=Megnyitás a &Konquerorral
+Description[is]=Opna með &Konqueror
+Description[it]=Apri con &Konqueror
+Description[ja]=Konqueror で開く(&K)
+Description[ka]=&Konqueror-ში გახსნა
+Description[kk]=&Konqueror-де ашу
+Description[km]=បើក​ជាមួយ &Konqueror
+Description[ko]=Konqueror로 열기(&K)
+Description[lo]=ເປີດດ້ວຍຄອນຄິວເຣ &Konquerorີ
+Description[lt]=Atverti su &Konqueror
+Description[lv]=Atvērt ar Ie&karotāju
+Description[mk]=Отвори со &Konqueror
+Description[mn]=&Конкюророор нээх
+Description[ms]=Buka dengan &Konqueror
+Description[mt]=Iftaħ b' &Konqueror
+Description[nb]=Åpne med &Konqueror
+Description[nds]=Mit &Konqueror opmaken
+Description[ne]=कन्क्वेररसँग खोल्नुहोस्
+Description[nl]=Openen met &Konqueror
+Description[nn]=Opna med &Konqueror
+Description[nso]=Bula ka &Konqueror
+Description[pa]=ਕੋਨਕਿਉਰੋਰ ਨਾਲ ਖੋਲੋ(&K)
+Description[pl]=Otwórz w &Konquerorze
+Description[pt]=Abrir com o &Konqueror
+Description[pt_BR]=Abrir com o &Konqueror
+Description[ro]=Deschide cu &Konqueror
+Description[ru]=Открыть в &Konqueror
+Description[rw]=Gufungura ukoresheje Konqueror
+Description[se]=Raba &Konquerorain
+Description[sk]=Otvoriť pomocou &Konqueror
+Description[sl]=Odpri s &Konquerorjem
+Description[sr]=Отвори помоћу &Konqueror-а
+Description[sr@Latn]=Otvori pomoću &Konqueror-a
+Description[sv]=Öppna med &Konqueror
+Description[ta]=கான்கொரர் உடன் திற
+Description[te]=(&K) కాంకెరర్ తొ తెరువు
+Description[tg]=Бо &Konqueror боз кунед
+Description[th]=เปิดด้วย&คอนเควอร์เรอร์
+Description[tr]=&Konqueror ile Aç
+Description[tt]=&Konqueror belän Aç
+Description[uk]=Відкрити з &Konqueror
+Description[uz]=Konqueror &bilan ochish
+Description[uz@cyrillic]=Konqueror &билан очиш
+Description[ven]=Vula nga &Konqueror
+Description[vi]=Mở bằng &Konqueror
+Description[wa]=Drovi avou &Konqueror
+Description[xh]=Vula nge &Konqueror
+Description[zh_CN]=用 &Konqueror 打开
+Description[zh_TW]=使用 &Konqueror 開啟
+Description[zu]=Vula nge-&Konqueror
+Enabled=true
+
+[Action_6/Command_1]
+Commandline=netscape -no-about-splash -remote openURL\(%s, new-window\)
+Description=Open with &Netscape
+Description[af]=Open met Netscape
+Description[ar]=إفتح بواسطة &Netscape
+Description[az]=&Netscape ilə Aç
+Description[be]=Адкрыць у &Netscape
+Description[bg]=О&тваряне с Netscape
+Description[bn]=নেটস্কেপ দিয়ে খোলো (&ন)
+Description[br]=Digeriñ gant &Netscape
+Description[bs]=Otvori u &Netscape
+Description[ca]=Obre amb el &Netscape
+Description[cs]=Otevřít pomocí &Netscape
+Description[csb]=Òtemknij w &Netscape
+Description[cy]=Agor gyda &Netscape
+Description[da]=Åbn med &Netscape
+Description[de]=Mit &Netscape öffnen
+Description[el]=Άνοιγμα με το &Netscape
+Description[eo]=Malfermi per &Netskapo
+Description[es]=Abrir con &Netscape
+Description[et]=&Netscape'is avamine
+Description[eu]= Ireki &Netscaperekin
+Description[fa]=باز کردن با &نت‌اسکیپ
+Description[fi]=Avaa &Netscapella
+Description[fo]=Opna við &Netscape
+Description[fr]=Ouvrir dans &Netscape
+Description[fy]=Iepenje mei &Netscape
+Description[ga]=Oscail le &Netscape
+Description[gl]=Abrir con &Netscape
+Description[he]=פתח באמצעות &Netscape
+Description[hi]=नेटस्केप के साथ खोलें (&N)
+Description[hr]=Otvori pomoću &Netscapea
+Description[hu]=Megnyitás a &Netscape-pel
+Description[is]=Opna með &Netscape
+Description[it]=Apri con &Netscape
+Description[ja]=Netscape で開く(&N)
+Description[ka]=&Netscape-სი გახსნა
+Description[kk]=&Netscape-те ашу
+Description[km]=បើក​ជាមួយ &Netscape
+Description[ko]=UADescription (윈도 XP의 넷스케이프 7.1)
+Description[lo]=ເປີດດ້ວຍເນ໊ຕສະເຄ໊ບ &Netscape
+Description[lt]=Atverti su &Netscape
+Description[lv]=Atvērt ar &Netscape
+Description[mk]=Отвори со &Netscape
+Description[mn]=&Nэтскапаар нээх
+Description[ms]=Buka dengan &Netscape
+Description[mt]=Iftaħ b' &Netscape
+Description[nb]=Åpne med &Netscape
+Description[nds]=Mit &Netscape opmaken
+Description[ne]=नेटस्केपसँग खोल्नुहोस्
+Description[nl]=Openen met &Netscape
+Description[nn]=Opna med &Netscape
+Description[nso]=Bula ka &Netscape
+Description[pa]=ਨੈੱਟਸਕੇਪ ਨਾਲ ਖੋਲੋ(&N)
+Description[pl]=Otwórz w &Netscape
+Description[pt]=Abrir com o &Netscape
+Description[pt_BR]=Abrir com o &Netscape
+Description[ro]=Deschide cu &Netscape
+Description[ru]=Открыть в &Netscape
+Description[rw]=Gufungura ukoresheje Netscape
+Description[se]=Raba &Netscapeain
+Description[sk]=Otvoriť pomocou &Netscape
+Description[sl]=Odpri z &Netscape
+Description[sr]=Отвори помоћу &Netscape-а
+Description[sr@Latn]=Otvori pomoću &Netscape-a
+Description[sv]=Öppna med &Netscape
+Description[ta]=நெட்ஸ்கேப்புடன் திற
+Description[te]=(&N) నెట్ స్కేప్ తొ తెరువు
+Description[tg]=Бо &Netscape боз кунед
+Description[th]=เปิดด้วยเน็ตสเคป
+Description[tr]=&Netscape ile Aç
+Description[tt]=&Netscape belän Aç
+Description[uk]=Відкрити з &Netscape
+Description[uz]=&Netscape bilan ochish
+Description[uz@cyrillic]=&Netscape билан очиш
+Description[ven]=Vula nga &Netscape
+Description[vi]=Mở bằng &Netscape
+Description[wa]=Drovi avou &Netscape
+Description[xh]=Vula nge &Netscape
+Description[zh_CN]=用 &Netscape 打开
+Description[zh_TW]=使用 &Netscape 開啟
+Description[zu]=Vula nge-&Netscape
+Enabled=true
+
+[Action_6/Command_2]
+Commandline=mozilla -remote openURL\(%s, new-window\)
+Description=Open with &Mozilla
+Description[af]=Maak oop met Mozilla
+Description[ar]=إفتح بِــ &Mozilla
+Description[az]=&Mozilla ilə Aç
+Description[be]=Адкрыць у &Mozilla
+Description[bg]=От&варяне с Mozilla
+Description[bn]=মোজিলা দিয়ে খোলো (&ম)
+Description[br]=Digeriñ gant &Mozilla
+Description[bs]=Otvori u &Mozilla
+Description[ca]=Obre amb el &Mozilla
+Description[cs]=Otevřít pomocí &Mozilla
+Description[csb]=Òtemknij w &Mozillë
+Description[cy]=Agor gyda &Mozilla
+Description[da]=Åbn med &Mozilla
+Description[de]=Mit &Mozilla öffnen
+Description[el]=Άνοιγμα με το &Mozilla
+Description[eo]=Malfermi per &Mozilo
+Description[es]=Abrir con &Mozilla
+Description[et]=&Mozillas avamine
+Description[eu]=Ireki &Mozillarekin
+Description[fa]=باز کردن با &موزیلا‌
+Description[fi]=Avaa &Mozillalla
+Description[fo]=Opna við &Mozilla
+Description[fr]=Ouvrir dans &Mozilla
+Description[fy]=Iepenje mei &Mozilla
+Description[ga]=Oscail le &Mozilla
+Description[gl]=Abrir con &Mozilla
+Description[he]=פתח באמצעות &Mozilla
+Description[hi]=मोज़िला के साथ खोलें (&M)
+Description[hr]=Otvori pomoću &Mozille
+Description[hu]=Megnyitás a &Mozillával
+Description[is]=Opna með &Mozilla
+Description[it]=Apri con &Mozilla
+Description[ja]=Mozilla で開く(&M)
+Description[ka]=&Mozilla-ში გასნა
+Description[kk]=&Mozilla-да ашу
+Description[km]=បើក​ជាមួយ Mozilla
+Description[ko]=Mozilla로 열기(&M)
+Description[lo]=ເປີດດ້ວຍ &Mozilla
+Description[lt]=Atverti su &Mozilla
+Description[lv]=Atvērt ar &Mozilla
+Description[mk]=Отвори со &Mozilla
+Description[mn]=&Mозиллагаар нээх
+Description[ms]=Buka dengan &Mozilla
+Description[mt]=Iftaħ b' &Możilla
+Description[nb]=Åpne med &Mozilla
+Description[nds]=Mit &Mozilla opmaken
+Description[ne]=मोजिलासँग खोल्नुहोस्
+Description[nl]=Openen met &Mozilla
+Description[nn]=Opna med &Mozilla
+Description[nso]=Bula ka &Mozilla
+Description[pa]=ਮੌਜੀਲਾ ਨਾਲ ਖੋਲੋ(&M)
+Description[pl]=Otwórz w &Mozilli
+Description[pt]=Abrir com o &Mozilla
+Description[pt_BR]=Abrir com o &Mozilla
+Description[ro]=Deschide cu &Mozilla
+Description[ru]=Открыть в &Mozilla
+Description[rw]=Gufungura ukoresheje Mozilla
+Description[se]=Raba &Mozillain
+Description[sk]=Otvoriť pomocou &Mozilla
+Description[sl]=Odpri z &Mozillo
+Description[sr]=Отвори помоћу &Mozilla-е
+Description[sr@Latn]=Otvori pomoću &Mozilla-e
+Description[sv]=Öppna med &Mozilla
+Description[ta]=மொசில்லாவுடன் திற
+Description[te]=(&M) మొజిల్లా తొ తెరువు
+Description[tg]=Бо &Mozilla боз кунед
+Description[th]=เปิดด้วย&มอซซิลลา
+Description[tr]=&Mozilla ile Aç
+Description[tt]=&Mozilla belän Aç
+Description[uk]=Відкрити з &Mozilla
+Description[uz]=Mozilla bilan &ochish
+Description[uz@cyrillic]=Mozilla билан &очиш
+Description[ven]=Vula nga &Mozilla
+Description[vi]=Mở bằng &Mozilla
+Description[wa]=Drovi avou &Mozilla
+Description[xh]=Vula nge &Mozilla
+Description[zh_CN]=用 &Mozilla 打开
+Description[zh_TW]=使用 &Mozilla 開啟
+Description[zu]=Vula nge-&Mozilla
+Enabled=true
+
+[Action_6/Command_3]
+Commandline=kmail --body %s
+Description=Send &URL
+Description[af]=Stuur URL
+Description[ar]=ارسال &رابط
+Description[az]=&URL Göndər
+Description[be]=Даслаць &URL
+Description[bg]=&Изпращане на адрес
+Description[bn]=ই&উ-আর-এল পাঠাও
+Description[br]=Kas an &URL
+Description[bs]=Pošalji &URL
+Description[ca]=Envia l'&URL
+Description[cs]=Poslat &URL
+Description[csb]=Wëslij adresã (&URL)
+Description[cy]=Anfon &URL
+Description[de]=&URL senden
+Description[el]=Αποστολή &URL
+Description[eo]=Sendi &URLon
+Description[es]=Enviar &URL
+Description[et]=&URL-i saatmine
+Description[eu]=Bidali &URLa
+Description[fa]=ارسال &نشانی ‌وب‌
+Description[fi]=Lähetä &URL
+Description[fr]=Envoyer l'&URL
+Description[fy]=&URL-adres stjoere
+Description[ga]=Seol &URL
+Description[gl]=Enviar &URL
+Description[he]=שלח &כתובת
+Description[hi]= यूआरएल भेजें (&U)
+Description[hr]=Pošalji &URL
+Description[hu]=Az &URL elküldése
+Description[is]=Senda &slóð
+Description[it]=Invia l'&URL
+Description[ja]=URL を送信(&U)
+Description[ka]=&URL-ს გაგზავნა
+Description[kk]=&URL-ды жіберу
+Description[km]=ផ្ញើ &URL
+Description[lo]=ສັ່ງຕຳແຫນ່ງ &URL
+Description[lt]=Siųsti &URL
+Description[lv]=Sūtīt &URL
+Description[mk]=Испрати &URL
+Description[mn]=&URL илгээх
+Description[ms]=Hantar &URL
+Description[mt]=Ibgħat &URL
+Description[nb]=Send &Nettadressen
+Description[nds]=&URL schicken
+Description[ne]=यूआरएल पठाउनुहोस्
+Description[nl]=&URL-adres verzenden
+Description[nso]=Romela &URL
+Description[pa]=&URL ਭੇਜੋ
+Description[pl]=Wyślij adres (&URL)
+Description[pt]=Enviar o &URL
+Description[pt_BR]=Enviar &URL
+Description[ro]=Trimite &URL-ul
+Description[ru]=Отправить &URL
+Description[rw]=Kohereza URL
+Description[se]=Sádde &URL:a
+Description[sk]=Poslať &URL
+Description[sl]=Pošlji &URL
+Description[sr]=Пошаљи &URL
+Description[sr@Latn]=Pošalji &URL
+Description[sv]=Skicka &webbadress
+Description[ta]=வலைப்பின்னலை அனுப்பு
+Description[te]=(&U) యూ ఆర్ ఎల్ పంపించు
+Description[tg]=Фиристодани &URL
+Description[th]=ส่งตำแหน่ง &URL
+Description[tr]=&URL Gönder
+Description[tt]=&URL Künderü
+Description[uk]=Відіслати &URL
+Description[uz]=URL'ni &joʻnatish
+Description[uz@cyrillic]=URL'ни &жўнатиш
+Description[ven]=Rumele &URL
+Description[vi]=Gửi&URL
+Description[wa]=Evoyî &hårdêye
+Description[xh]=Thumela i &URL
+Description[zh_CN]=发送 &URL
+Description[zh_TW]=送出 &URL
+Description[zu]=Thumela i-&URL
+Enabled=true
+
+[Action_6/Command_4]
+Commandline=kmail --attach %s
+Description=Send &File
+Description[af]=Stuur Lêer
+Description[ar]=ارسال م&لف
+Description[az]=&Fayl Göndər
+Description[be]=Даслаць &файл
+Description[bg]=Изпращане на &файл
+Description[bn]=ফাই&ল পাঠাও
+Description[br]=Kas ar &restr
+Description[bs]=Pošalji &Datoteku
+Description[ca]=Envia &fitxer
+Description[cs]=Posl&at soubor
+Description[csb]=Wëslij &lopk
+Description[cy]=Anfon &Ffeil
+Description[da]=Send &fil
+Description[de]=&Datei senden
+Description[el]=Αποστολή &αρχείου
+Description[eo]=Sendi &dosieron
+Description[es]=Enviar &archivo
+Description[et]=&Faili saatmine
+Description[eu]=Bidali &Fitxategia
+Description[fa]=ارسال &پرونده‌
+Description[fi]=Lähetä &tiedosto
+Description[fr]=Envoyer le &fichier
+Description[fy]=&Triem stjoere
+Description[ga]=Seol &Comhad
+Description[gl]=Enviar &Ficheiro
+Description[he]=שלח &קובץ
+Description[hi]=फ़ाइल भेजें (&F)
+Description[hr]=Pošalji &datoteku
+Description[hu]=&Fájl elküldése
+Description[is]=Senda &skrá
+Description[it]=Invia il &file
+Description[ja]=ファイルを送信(&F)
+Description[ka]=&ფაილის გაგზავნა
+Description[kk]=&Файлды жіберу
+Description[km]=ផ្ញើ​ឯកសារ
+Description[ko]=파일 보내기(&F)
+Description[lo]=ສົ່ງແຟ້ມ &File
+Description[lt]=Siųsti &bylą
+Description[lv]=Sūtīt &Failu
+Description[mk]=Испрати &датотека
+Description[mn]=&Файл илгээх
+Description[ms]=Hantar &Fail
+Description[mt]=Ibgħat &fajl
+Description[nb]=Send &Fil
+Description[nds]=Datei &schicken
+Description[ne]=फाइल पठाउनुहोस्
+Description[nl]=&Bestand verzenden
+Description[nn]=Send &fil
+Description[nso]=Romela &Faele
+Description[pa]=ਫਾਇਲ ਭੇਜੋ(&F)
+Description[pl]=Wyślij &plik
+Description[pt]=Enviar o &Ficheiro
+Description[pt_BR]=Enviar &Arquivo
+Description[ro]=Trimite &fișierul
+Description[ru]=Отправить &файл
+Description[rw]=Kohereza Idosiye
+Description[se]=Sádde &fiilla
+Description[sk]=Poslať &súbor
+Description[sl]=Pošlji &datoteko
+Description[sr]=Пошаљи &фајл
+Description[sr@Latn]=Pošalji &fajl
+Description[sv]=Skicka &fil
+Description[ta]=கோப்பை அனுப்பு
+Description[te]=(&F) దస్త్రాన్ని పంపించు
+Description[tg]=Фиристодани &файл
+Description[th]=ส่งแฟ้ม
+Description[tr]=Dosya &Gönder
+Description[tt]=Bir&em Cibärü
+Description[uk]=Відіслати &File
+Description[uz]=&Faylni joʻnatish
+Description[uz@cyrillic]=&Файлни жўнатиш
+Description[ven]=Rumela &Faela
+Description[vi]=Gửi &Tập tin
+Description[wa]=Evoyî &fitchî
+Description[xh]=Thumela &Ifayile
+Description[zh_CN]=发送文件(&F)
+Description[zh_TW]=送出檔案(&F)
+Description[zu]=Thumela &Ifayela
+Enabled=true
+
+[General]
+KeepClipboardContents=true
+No Actions for WM_CLASS=Navigator,navigator:browser,konqueror,keditbookmarks,mozilla-bin,Mozilla,Opera main window,opera,gnumeric,Gnumeric,Galeon,kcontrol,ksirc,MozillaFirebird-bin,firefox-bin,Firefox-bin,klipper,Gecko,gecko
+Number of Actions=7
+PopupAtMousePosition=false
+URLGrabberEnabled=false
diff --git a/klipper/klipperrc.upd b/klipper/klipperrc.upd
new file mode 100644
index 000000000..943ec9e20
--- /dev/null
+++ b/klipper/klipperrc.upd
@@ -0,0 +1,7 @@
+Id=25082001
+File=klipperrc
+Script=klipper-1-2.pl,perl
+Id=kde3.1
+File=klipperrc
+Group=General
+Script=klipper-kde31.sh,sh
diff --git a/klipper/klippershortcuts.upd b/klipper/klippershortcuts.upd
new file mode 100644
index 000000000..1088e054c
--- /dev/null
+++ b/klipper/klippershortcuts.upd
@@ -0,0 +1,5 @@
+Id=04112002
+File=klipperrc,kdeglobals
+Group=Global Shortcuts
+AllKeys
+RemoveGroup=Global Shortcuts
diff --git a/klipper/main.cpp b/klipper/main.cpp
new file mode 100644
index 000000000..cb54a0e68
--- /dev/null
+++ b/klipper/main.cpp
@@ -0,0 +1,67 @@
+// -*- Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 8; -*-
+/* This file is part of the KDE project
+ Copyright (C) Andrew Stanley-Jones
+
+ 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; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <klocale.h>
+#include <kcmdlineargs.h>
+#include <kwin.h>
+#include <kaboutdata.h>
+#include <kuniqueapplication.h>
+
+#include "toplevel.h"
+#include "version.h"
+
+#if defined Q_WS_X11
+#include <qxembed.h> // schroder
+#endif
+
+
+extern "C" int KDE_EXPORT kdemain(int argc, char *argv[])
+{
+ Klipper::createAboutData();
+ KCmdLineArgs::init( argc, argv, Klipper::aboutData());
+ KUniqueApplication::addCmdLineOptions();
+
+ if (!KUniqueApplication::start()) {
+ fprintf(stderr, "Klipper is already running!\n");
+ exit(0);
+ }
+ KUniqueApplication app;
+ app.disableSessionManagement();
+
+ Klipper *toplevel = new Klipper();
+
+ // Make Klipper conform to freedesktop system tray standard, see
+ // http://bugs.kde.org/show_bug.cgi?id=69119
+#if defined Q_WS_X11 && ! defined K_WS_QTONLY
+ QXEmbed::initialize();
+#endif
+
+ KWin::setSystemTrayWindowFor( toplevel->winId(), 0 );
+ toplevel->setGeometry(-100, -100, 42, 42 );
+ toplevel->show();
+
+ int ret = app.exec();
+ delete toplevel;
+ Klipper::destroyAboutData();
+ return ret;
+}
diff --git a/klipper/popupproxy.cpp b/klipper/popupproxy.cpp
new file mode 100644
index 000000000..84a9e4ae6
--- /dev/null
+++ b/klipper/popupproxy.cpp
@@ -0,0 +1,163 @@
+// -*- Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 8; -*-
+/* This file is part of the KDE project
+ Copyright (C) 2004 Esben Mose Hansen <[email protected]>
+
+ 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; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+#include <qregexp.h>
+#include <qstyle.h>
+#include <qpixmap.h>
+#include <qimage.h>
+
+#include <kstringhandler.h>
+#include <klocale.h>
+#include <kdebug.h>
+
+#include "historyitem.h"
+#include "popupproxy.h"
+#include "history.h"
+#include "klipperpopup.h"
+
+
+PopupProxy::PopupProxy( KlipperPopup* parent, const char* name, int menu_height, int menu_width )
+ : QObject( parent, name ),
+ proxy_for_menu( parent ),
+ spillPointer( parent->history()->youngest() ),
+ m_menu_height( menu_height ),
+ m_menu_width( menu_width ),
+ nextItemNumber( 0 )
+{
+ connect( parent->history(), SIGNAL( changed() ), SLOT( slotHistoryChanged() ) );
+}
+
+void PopupProxy::slotHistoryChanged() {
+ deleteMoreMenus();
+
+}
+
+void PopupProxy::deleteMoreMenus() {
+ const KPopupMenu* myParent = parent();
+ if ( myParent != proxy_for_menu ) {
+ const KPopupMenu* delme = proxy_for_menu;;
+ proxy_for_menu = static_cast<KPopupMenu*>( proxy_for_menu->parent() );
+ while ( proxy_for_menu != myParent ) {
+ delme = proxy_for_menu;
+ proxy_for_menu = static_cast<KPopupMenu*>( proxy_for_menu->parent() );
+ }
+ delete delme;
+ }
+}
+
+int PopupProxy::buildParent( int index, const QRegExp& filter ) {
+ deleteMoreMenus();
+ // Start from top of history (again)
+ spillPointer = parent()->history()->youngest();
+ nextItemNumber = 0;
+ if ( filter.isValid() ) {
+ m_filter = filter;
+ }
+
+ return insertFromSpill( index );
+
+}
+
+KlipperPopup* PopupProxy::parent() {
+ return static_cast<KlipperPopup*>( QObject::parent() );
+}
+
+void PopupProxy::slotAboutToShow() {
+ insertFromSpill();
+}
+
+void PopupProxy::tryInsertItem( HistoryItem const * const item,
+ int& remainingHeight,
+ const int index )
+{
+
+ // Insert item
+ int id = -1;
+ QPixmap image( item->image() );
+ if ( image.isNull() ) {
+ // Squeeze text strings so that do not take up the entire screen (or more)
+ QString text( KStringHandler::cPixelSqueeze(item->text().simplifyWhiteSpace(),
+ proxy_for_menu->fontMetrics(),
+ m_menu_width).replace( "&", "&&" ) );
+ id = proxy_for_menu->insertItem( text, -1, index );
+ } else {
+ const QSize max_size( m_menu_width,m_menu_height/4 );
+ if ( image.height() > max_size.height() || image.width() > max_size.width() ) {
+ image.convertFromImage( image.convertToImage().smoothScale( max_size, QImage::ScaleMin ) );
+ }
+ id = proxy_for_menu->insertItem( image, -1, index );
+ }
+
+
+ // Determine height of a menu item.
+ Q_ASSERT( id != -1 ); // Be sure that the item was inserted.
+ QMenuItem* mi = proxy_for_menu->findItem( id );
+ int fontheight = QFontMetrics( proxy_for_menu->fontMetrics() ).height();
+ int itemheight = proxy_for_menu->style().sizeFromContents(QStyle::CT_PopupMenuItem,
+ proxy_for_menu,
+ QSize( 0, fontheight ),
+ QStyleOption(mi,10,0) ).height();
+ // Test if there was enough space
+ remainingHeight -= itemheight;
+ History* history = parent()->history();
+ proxy_for_menu->connectItem( id,
+ history,
+ SLOT( slotMoveToTop( int ) ) );
+ proxy_for_menu->setItemParameter( id, nextItemNumber );
+
+}
+
+int PopupProxy::insertFromSpill( int index ) {
+
+ // This menu is going to be filled, so we don't need the aboutToShow()
+ // signal anymore
+ disconnect( proxy_for_menu, 0, this, 0 );
+
+ // Insert history items into the current proxy_for_menu,
+ // discarding any that doesn't match the current filter.
+ // stop when the total number of items equal m_itemsPerMenu;
+ int count = 0;
+ int remainingHeight = m_menu_height - proxy_for_menu->sizeHint().height();
+ // Force at least one item to be inserted.
+ remainingHeight = QMAX( remainingHeight, 0 );
+ for ( const HistoryItem* item = spillPointer.current();
+ item && remainingHeight >= 0;
+ nextItemNumber++, item = ++spillPointer )
+ {
+ if ( m_filter.search( item->text() ) == -1) {
+ continue;
+ }
+ tryInsertItem( item, remainingHeight, index++ );
+ count++;
+ }
+
+ // If there is more items in the history, insert a new "More..." menu and
+ // make *this a proxy for that menu ('s content).
+ if ( spillPointer.current() ) {
+ KPopupMenu* moreMenu = new KPopupMenu( proxy_for_menu, "a more menu" );
+ proxy_for_menu->insertItem( i18n( "&More" ), moreMenu, -1, index );
+ connect( moreMenu, SIGNAL( aboutToShow() ), SLOT( slotAboutToShow() ) );
+ proxy_for_menu = moreMenu;
+ }
+
+ // Return the number of items inserted.
+ return count;
+
+}
+#include "popupproxy.moc"
diff --git a/klipper/popupproxy.h b/klipper/popupproxy.h
new file mode 100644
index 000000000..689e97f2a
--- /dev/null
+++ b/klipper/popupproxy.h
@@ -0,0 +1,93 @@
+// -*- Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 8; -*-
+/* This file is part of the KDE project
+ Copyright (C) 2004 Esben Mose Hansen <[email protected]>
+
+ 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; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+#ifndef _POPUPPROXY_H_
+#define _POPUPPROXY_H_
+
+#include <qptrlist.h>
+#include <qobject.h>
+#include <qstring.h>
+#include <qregexp.h>
+
+#include <kpopupmenu.h>
+#include <history.h>
+
+class HistoryItem;
+class KlipperPopup;
+
+/**
+ * Proxy helper for the "more" menu item
+ *
+ */
+class PopupProxy : public QObject
+{
+ Q_OBJECT
+
+public:
+ /**
+ * Inserts up to itemsPerMenu into parent from parent->youngest(),
+ * and spills any remaining items into a more menu.
+ */
+ PopupProxy( KlipperPopup* parent, const char* name, int menu_height, int menu_width );
+
+ KlipperPopup* parent();
+
+ /**
+ * Called when rebuilding the menu
+ * Deletes any More menus.. and start (re)inserting into the toplevel menu.
+ * @param index Items are inserted at index.
+ * @param filter If non-empty, only insert items that match filter as a regex
+ * @return number of items inserted.
+ */
+ int buildParent( int index, const QRegExp& filter = QRegExp() );
+
+public slots:
+ void slotAboutToShow();
+ void slotHistoryChanged();
+private:
+ /**
+ * Insert up to m_itemsPerMenu items from spill and a new
+ * more-menu if neccessary.
+ * @param index Items are inserted at index
+ * @return number of items inserted.
+ */
+ int insertFromSpill( int index = 0 );
+
+ /**
+ * Insert item into proxy_for_menu at index,
+ * subtracting the items height from remainingHeight
+ */
+ void tryInsertItem( HistoryItem const * const item, int& remainingHeight, const int index );
+
+ /**
+ * Delete all "More..." menus current created.
+ */
+ void deleteMoreMenus();
+
+private:
+ KPopupMenu* proxy_for_menu;
+ History::iterator spillPointer;
+ QRegExp m_filter;
+ int m_menu_height;
+ int m_menu_width;
+ int nextItemNumber;
+
+};
+
+#endif
diff --git a/klipper/toplevel.cpp b/klipper/toplevel.cpp
new file mode 100644
index 000000000..cc5de3f63
--- /dev/null
+++ b/klipper/toplevel.cpp
@@ -0,0 +1,1174 @@
+// -*- Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 8; -*-
+/* This file is part of the KDE project
+
+ Copyright (C) by Andrew Stanley-Jones
+ Copyright (C) 2000 by Carsten Pfeiffer <[email protected]>
+ Copyright (C) 2004 Esben Mose Hansen <[email protected]>
+
+ 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; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#include <qclipboard.h>
+#include <qcursor.h>
+#include <qdatetime.h>
+#include <qfile.h>
+#include <qintdict.h>
+#include <qpainter.h>
+#include <qtooltip.h>
+#include <qmime.h>
+#include <qdragobject.h>
+
+#include <kaboutdata.h>
+#include <kaction.h>
+#include <kapplication.h>
+#include <kconfig.h>
+#include <kglobal.h>
+#include <kipc.h>
+#include <kkeydialog.h>
+#include <klocale.h>
+#include <kmessagebox.h>
+#include <ksavefile.h>
+#include <kstandarddirs.h>
+#include <ksimpleconfig.h>
+#include <kstringhandler.h>
+#include <ksystemtray.h>
+#include <kurldrag.h>
+#include <kwin.h>
+#include <kdebug.h>
+#include <kglobalsettings.h>
+#include <dcopclient.h>
+#include <kiconloader.h>
+#include <khelpmenu.h>
+#include <kstdguiitem.h>
+
+#include "configdialog.h"
+#include "toplevel.h"
+#include "urlgrabber.h"
+#include "version.h"
+#include "clipboardpoll.h"
+#include "history.h"
+#include "historyitem.h"
+#include "historystringitem.h"
+#include "klipperpopup.h"
+
+#include <zlib.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+
+#include <kclipboard.h>
+
+namespace {
+ /**
+ * Use this when manipulating the clipboard
+ * from within clipboard-related signals.
+ *
+ * This avoids issues such as mouse-selections that immediately
+ * disappear.
+ * pattern: Resource Acqusition is Initialisation (RAII)
+ *
+ * (This is not threadsafe, so don't try to use such in threaded
+ * applications).
+ */
+ struct Ignore {
+ Ignore(int& locklevel) : locklevelref(locklevel) {
+ locklevelref++;
+ }
+ ~Ignore() {
+ locklevelref--;
+ }
+ private:
+ int& locklevelref;
+ };
+}
+
+/**
+ * Helper class to save history upon session exit.
+ */
+class KlipperSessionManaged : public KSessionManaged
+{
+public:
+ KlipperSessionManaged( KlipperWidget* k )
+ : klipper( k )
+ {}
+
+ /**
+ * Save state upon session exit.
+ *
+ * Saving history on session save
+ */
+ virtual bool commitData( QSessionManager& ) {
+ klipper->saveSession();
+ return true;
+ }
+private:
+ KlipperWidget* klipper;
+};
+
+extern bool qt_qclipboard_bailout_hack;
+#if KDE_IS_VERSION( 3, 9, 0 )
+#error Check status of #80072 with Qt4.
+#endif
+
+static void ensureGlobalSyncOff(KConfig* config);
+
+// config == kapp->config for process, otherwise applet
+KlipperWidget::KlipperWidget( QWidget *parent, KConfig* config )
+ : QWidget( parent )
+ , DCOPObject( "klipper" )
+ , m_overflowCounter( 0 )
+ , locklevel( 0 )
+ , m_config( config )
+ , m_pendingContentsCheck( false )
+ , session_managed( new KlipperSessionManaged( this ))
+{
+ qt_qclipboard_bailout_hack = true;
+
+ // We don't use the clipboardsynchronizer anymore, and it confuses Klipper
+ ensureGlobalSyncOff(m_config);
+
+ updateTimestamp(); // read initial X user time
+ setBackgroundMode( X11ParentRelative );
+ clip = kapp->clipboard();
+
+ connect( &m_overflowClearTimer, SIGNAL( timeout()), SLOT( slotClearOverflow()));
+ m_overflowClearTimer.start( 1000 );
+ connect( &m_pendingCheckTimer, SIGNAL( timeout()), SLOT( slotCheckPending()));
+
+ m_history = new History( this, "main_history" );
+
+ // we need that collection, otherwise KToggleAction is not happy :}
+ QString defaultGroup( "default" );
+ KActionCollection *collection = new KActionCollection( this, "my collection" );
+ toggleURLGrabAction = new KToggleAction( collection, "toggleUrlGrabAction" );
+ toggleURLGrabAction->setEnabled( true );
+ toggleURLGrabAction->setGroup( defaultGroup );
+ clearHistoryAction = new KAction( i18n("C&lear Clipboard History"),
+ "history_clear",
+ 0,
+ history(),
+ SLOT( slotClear() ),
+ collection,
+ "clearHistoryAction" );
+ connect( clearHistoryAction, SIGNAL( activated() ), SLOT( slotClearClipboard() ) );
+ clearHistoryAction->setGroup( defaultGroup );
+ configureAction = new KAction( i18n("&Configure Klipper..."),
+ "configure",
+ 0,
+ this,
+ SLOT( slotConfigure() ),
+ collection,
+ "configureAction" );
+ configureAction->setGroup( defaultGroup );
+ quitAction = new KAction( i18n("&Quit"),
+ "exit",
+ 0,
+ this,
+ SLOT( slotQuit() ),
+ collection,
+ "quitAction" );
+ quitAction->setGroup( "exit" );
+ myURLGrabber = 0L;
+ KConfig *kc = m_config;
+ readConfiguration( kc );
+ setURLGrabberEnabled( bURLGrabber );
+
+ hideTimer = new QTime();
+ showTimer = new QTime();
+
+ readProperties(m_config);
+ connect(kapp, SIGNAL(settingsChanged(int)), SLOT(slotSettingsChanged(int)));
+
+ poll = new ClipboardPoll( this );
+ connect( poll, SIGNAL( clipboardChanged( bool ) ),
+ this, SLOT( newClipData( bool ) ) );
+
+ m_pixmap = KSystemTray::loadIcon( "klipper" );
+ adjustSize();
+
+ globalKeys = new KGlobalAccel(this);
+ KGlobalAccel* keys = globalKeys;
+#include "klipperbindings.cpp"
+ // the keys need to be read from kdeglobals, not kickerrc --ellis, 22/9/02
+ globalKeys->readSettings();
+ globalKeys->updateConnections();
+ toggleURLGrabAction->setShortcut(globalKeys->shortcut("Enable/Disable Clipboard Actions"));
+
+ connect( toggleURLGrabAction, SIGNAL( toggled( bool )),
+ this, SLOT( setURLGrabberEnabled( bool )));
+
+ KlipperPopup* popup = history()->popup();
+ connect ( history(), SIGNAL( topChanged() ), SLOT( slotHistoryTopChanged() ) );
+ connect( popup, SIGNAL( aboutToHide() ), SLOT( slotStartHideTimer() ) );
+ connect( popup, SIGNAL( aboutToShow() ), SLOT( slotStartShowTimer() ) );
+
+ popup->plugAction( toggleURLGrabAction );
+ popup->plugAction( clearHistoryAction );
+ popup->plugAction( configureAction );
+ if ( !isApplet() ) {
+ popup->plugAction( quitAction );
+ }
+
+ QToolTip::add( this, i18n("Klipper - clipboard tool") );
+}
+
+KlipperWidget::~KlipperWidget()
+{
+ delete session_managed;
+ delete showTimer;
+ delete hideTimer;
+ delete myURLGrabber;
+ if( m_config != kapp->config())
+ delete m_config;
+ qt_qclipboard_bailout_hack = false;
+}
+
+void KlipperWidget::adjustSize()
+{
+ resize( m_pixmap.size() );
+}
+
+// DCOP
+QString KlipperWidget::getClipboardContents()
+{
+ return getClipboardHistoryItem(0);
+}
+
+// DCOP - don't call from Klipper itself
+void KlipperWidget::setClipboardContents(QString s)
+{
+ Ignore lock( locklevel );
+ updateTimestamp();
+ HistoryStringItem* item = new HistoryStringItem( s );
+ setClipboard( *item, Clipboard | Selection);
+ history()->insert( item );
+}
+
+// DCOP - don't call from Klipper itself
+void KlipperWidget::clearClipboardContents()
+{
+ updateTimestamp();
+ slotClearClipboard();
+}
+
+// DCOP - don't call from Klipper itself
+void KlipperWidget::clearClipboardHistory()
+{
+ updateTimestamp();
+ slotClearClipboard();
+ history()->slotClear();
+ saveSession();
+}
+
+
+void KlipperWidget::mousePressEvent(QMouseEvent *e)
+{
+ if ( e->button() != LeftButton && e->button() != RightButton )
+ return;
+
+ // if we only hid the menu less than a third of a second ago,
+ // it's probably because the user clicked on the klipper icon
+ // to hide it, and therefore won't want it shown again.
+ if ( hideTimer->elapsed() > 300 ) {
+ slotPopupMenu();
+ }
+}
+
+void KlipperWidget::paintEvent(QPaintEvent *)
+{
+ QPainter p(this);
+ int x = (width() - m_pixmap.width()) / 2;
+ int y = (height() - m_pixmap.height()) / 2;
+ if ( x < 0 ) x = 0;
+ if ( y < 0 ) y = 0;
+ p.drawPixmap(x , y, m_pixmap);
+ p.end();
+}
+
+void KlipperWidget::slotStartHideTimer()
+{
+ hideTimer->start();
+}
+
+void KlipperWidget::slotStartShowTimer()
+{
+ showTimer->start();
+}
+
+void KlipperWidget::showPopupMenu( QPopupMenu *menu )
+{
+ Q_ASSERT( menu != 0L );
+
+ QSize size = menu->sizeHint(); // geometry is not valid until it's shown
+ if (bPopupAtMouse) {
+ QPoint g = QCursor::pos();
+ if ( size.height() < g.y() )
+ menu->popup(QPoint( g.x(), g.y() - size.height()));
+ else
+ menu->popup(QPoint(g.x(), g.y()));
+ } else {
+ KWin::WindowInfo i = KWin::windowInfo( winId(), NET::WMGeometry );
+ QRect g = i.geometry();
+ QRect screen = KGlobalSettings::desktopGeometry(g.center());
+
+ if ( g.x()-screen.x() > screen.width()/2 &&
+ g.y()-screen.y() + size.height() > screen.height() )
+ menu->popup(QPoint( g.x(), g.y() - size.height()));
+ else
+ menu->popup(QPoint( g.x() + width(), g.y() + height()));
+
+ // menu->exec(mapToGlobal(QPoint( width()/2, height()/2 )));
+ }
+}
+
+bool KlipperWidget::loadHistory() {
+ static const char* const failed_load_warning =
+ "Failed to load history resource. Clipboard history cannot be read.";
+ // don't use "appdata", klipper is also a kicker applet
+ QString history_file_name = ::locateLocal( "data", "klipper/history2.lst" );
+ QFile history_file( history_file_name );
+ bool oldfile = false;
+ if ( !history_file.exists() ) { // backwards compatibility
+ oldfile = true;
+ history_file_name = ::locateLocal( "data", "klipper/history.lst" );
+ history_file.setName( history_file_name );
+ if ( !history_file.exists() ) {
+ history_file_name = ::locateLocal( "data", "kicker/history.lst" );
+ history_file.setName( history_file_name );
+ if ( !history_file.exists() ) {
+ return false;
+ }
+ }
+ }
+ if ( !history_file.open( IO_ReadOnly ) ) {
+ kdWarning() << failed_load_warning << ": " << history_file.errorString() << endl;
+ return false;
+ }
+ QDataStream file_stream( &history_file );
+ if( file_stream.atEnd()) {
+ kdWarning() << failed_load_warning << endl;
+ return false;
+ }
+ QDataStream* history_stream = &file_stream;
+ QByteArray data;
+ if( !oldfile ) {
+ Q_UINT32 crc;
+ file_stream >> crc >> data;
+ if( crc32( 0, reinterpret_cast<unsigned char *>( data.data() ), data.size() ) != crc ) {
+ kdWarning() << failed_load_warning << ": " << history_file.errorString() << endl;
+ return false;
+ }
+
+ history_stream = new QDataStream( data, IO_ReadOnly );
+ }
+ char* version;
+ *history_stream >> version;
+ delete[] version;
+
+ // The list needs to be reversed, as it is saved
+ // youngest-first to keep the most important clipboard
+ // items at the top, but the history is created oldest
+ // first.
+ QPtrList<HistoryItem> reverseList;
+ for ( HistoryItem* item = HistoryItem::create( *history_stream );
+ item;
+ item = HistoryItem::create( *history_stream ) )
+ {
+ reverseList.prepend( item );
+ }
+
+ for ( HistoryItem* item = reverseList.first();
+ item;
+ item = reverseList.next() )
+ {
+ history()->forceInsert( item );
+ }
+
+ if ( !history()->empty() ) {
+ m_lastSelection = -1;
+ m_lastClipboard = -1;
+ setClipboard( *history()->first(), Clipboard | Selection );
+ }
+
+ if( history_stream != &file_stream )
+ delete history_stream;
+
+ return true;
+}
+
+void KlipperWidget::saveHistory() {
+ static const char* const failed_save_warning =
+ "Failed to save history. Clipboard history cannot be saved.";
+ // don't use "appdata", klipper is also a kicker applet
+ QString history_file_name( ::locateLocal( "data", "klipper/history2.lst" ) );
+ if ( history_file_name.isNull() || history_file_name.isEmpty() ) {
+ kdWarning() << failed_save_warning << endl;
+ return;
+ }
+ KSaveFile history_file( history_file_name );
+ if ( history_file.status() != 0 ) {
+ kdWarning() << failed_save_warning << endl;
+ return;
+ }
+ QByteArray data;
+ QDataStream history_stream( data, IO_WriteOnly );
+ history_stream << klipper_version; // const char*
+ for ( const HistoryItem* item = history()->first(); item; item = history()->next() ) {
+ history_stream << item;
+ }
+ Q_UINT32 crc = crc32( 0, reinterpret_cast<unsigned char *>( data.data() ), data.size() );
+ *history_file.dataStream() << crc << data;
+}
+
+void KlipperWidget::readProperties(KConfig *kc)
+{
+ QStringList dataList;
+
+ history()->slotClear();
+
+ if (bKeepContents) { // load old clipboard if configured
+ if ( !loadHistory() ) {
+ // Try to load from the old config file.
+ // Remove this at some point.
+ KConfigGroupSaver groupSaver(kc, "General");
+ dataList = kc->readListEntry("ClipboardData");
+
+ for (QStringList::ConstIterator it = dataList.end();
+ it != dataList.begin();
+ )
+ {
+ history()->forceInsert( new HistoryStringItem( *( --it ) ) );
+ }
+
+ if ( !dataList.isEmpty() )
+ {
+ m_lastSelection = -1;
+ m_lastClipboard = -1;
+ setClipboard( *history()->first(), Clipboard | Selection );
+ }
+ }
+ }
+
+}
+
+
+void KlipperWidget::readConfiguration( KConfig *kc )
+{
+ kc->setGroup("General");
+ bPopupAtMouse = kc->readBoolEntry("PopupAtMousePosition", false);
+ bKeepContents = kc->readBoolEntry("KeepClipboardContents", true);
+ bURLGrabber = kc->readBoolEntry("URLGrabberEnabled", false);
+ bReplayActionInHistory = kc->readBoolEntry("ReplayActionInHistory", false);
+ bNoNullClipboard = kc->readBoolEntry("NoEmptyClipboard", true);
+ bUseGUIRegExpEditor = kc->readBoolEntry("UseGUIRegExpEditor", true );
+ history()->max_size( kc->readNumEntry("MaxClipItems", 7) );
+ bIgnoreSelection = kc->readBoolEntry("IgnoreSelection", false);
+ bSynchronize = kc->readBoolEntry("Synchronize", false);
+ bSelectionTextOnly = kc->readBoolEntry("SelectionTextOnly",true);
+ bIgnoreImages = kc->readBoolEntry("IgnoreImages",true);
+}
+
+void KlipperWidget::writeConfiguration( KConfig *kc )
+{
+ kc->setGroup("General");
+ kc->writeEntry("PopupAtMousePosition", bPopupAtMouse);
+ kc->writeEntry("KeepClipboardContents", bKeepContents);
+ kc->writeEntry("ReplayActionInHistory", bReplayActionInHistory);
+ kc->writeEntry("NoEmptyClipboard", bNoNullClipboard);
+ kc->writeEntry("UseGUIRegExpEditor", bUseGUIRegExpEditor);
+ kc->writeEntry("MaxClipItems", history()->max_size() );
+ kc->writeEntry("IgnoreSelection", bIgnoreSelection);
+ kc->writeEntry("Synchronize", bSynchronize );
+ kc->writeEntry("SelectionTextOnly", bSelectionTextOnly);
+ kc->writeEntry("TrackImages", bIgnoreImages);
+ kc->writeEntry("Version", klipper_version );
+
+ if ( myURLGrabber )
+ myURLGrabber->writeConfiguration( kc );
+
+ kc->sync();
+}
+
+// save session on shutdown. Don't simply use the c'tor, as that may not be called.
+void KlipperWidget::saveSession()
+{
+ if ( bKeepContents ) { // save the clipboard eventually
+ saveHistory();
+ }
+}
+
+void KlipperWidget::slotSettingsChanged( int category )
+{
+ if ( category == (int) KApplication::SETTINGS_SHORTCUTS ) {
+ globalKeys->readSettings();
+ globalKeys->updateConnections();
+ toggleURLGrabAction->setShortcut(globalKeys->shortcut("Enable/Disable Clipboard Actions"));
+ }
+}
+
+void KlipperWidget::disableURLGrabber()
+{
+ KMessageBox::information( 0L,
+ i18n( "You can enable URL actions later by right-clicking on the "
+ "Klipper icon and selecting 'Enable Actions'" ) );
+
+ setURLGrabberEnabled( false );
+}
+
+void KlipperWidget::slotConfigure()
+{
+ bool haveURLGrabber = bURLGrabber;
+ if ( !myURLGrabber ) { // temporary, for the config-dialog
+ setURLGrabberEnabled( true );
+ readConfiguration( m_config );
+ }
+
+ ConfigDialog *dlg = new ConfigDialog( myURLGrabber->actionList(),
+ globalKeys, isApplet() );
+ dlg->setKeepContents( bKeepContents );
+ dlg->setPopupAtMousePos( bPopupAtMouse );
+ dlg->setStripWhiteSpace( myURLGrabber->stripWhiteSpace() );
+ dlg->setReplayActionInHistory( bReplayActionInHistory );
+ dlg->setNoNullClipboard( bNoNullClipboard );
+ dlg->setUseGUIRegExpEditor( bUseGUIRegExpEditor );
+ dlg->setPopupTimeout( myURLGrabber->popupTimeout() );
+ dlg->setMaxItems( history()->max_size() );
+ dlg->setIgnoreSelection( bIgnoreSelection );
+ dlg->setSynchronize( bSynchronize );
+ dlg->setNoActionsFor( myURLGrabber->avoidWindows() );
+
+ if ( dlg->exec() == QDialog::Accepted ) {
+ bKeepContents = dlg->keepContents();
+ bPopupAtMouse = dlg->popupAtMousePos();
+ bReplayActionInHistory = dlg->replayActionInHistory();
+ bNoNullClipboard = dlg->noNullClipboard();
+ bIgnoreSelection = dlg->ignoreSelection();
+ bSynchronize = dlg->synchronize();
+ bUseGUIRegExpEditor = dlg->useGUIRegExpEditor();
+ dlg->commitShortcuts();
+ // the keys need to be written to kdeglobals, not kickerrc --ellis, 22/9/02
+ globalKeys->writeSettings(0, true);
+ globalKeys->updateConnections();
+ toggleURLGrabAction->setShortcut(globalKeys->shortcut("Enable/Disable Clipboard Actions"));
+
+ myURLGrabber->setActionList( dlg->actionList() );
+ myURLGrabber->setPopupTimeout( dlg->popupTimeout() );
+ myURLGrabber->setStripWhiteSpace( dlg->stripWhiteSpace() );
+ myURLGrabber->setAvoidWindows( dlg->noActionsFor() );
+
+ history()->max_size( dlg->maxItems() );
+
+ writeConfiguration( m_config );
+
+ }
+ setURLGrabberEnabled( haveURLGrabber );
+
+ delete dlg;
+}
+
+void KlipperWidget::slotQuit()
+{
+ // If the menu was just opened, likely the user
+ // selected quit by accident while attempting to
+ // click the Klipper icon.
+ if ( showTimer->elapsed() < 300 ) {
+ return;
+ }
+
+ saveSession();
+ int autoStart = KMessageBox::questionYesNoCancel( 0L, i18n("Should Klipper start automatically\nwhen you login?"), i18n("Automatically Start Klipper?"), i18n("Start"), i18n("Do Not Start") );
+
+ KConfig *config = KGlobal::config();
+ config->setGroup("General");
+ if ( autoStart == KMessageBox::Yes ) {
+ config->writeEntry("AutoStart", true);
+ } else if ( autoStart == KMessageBox::No) {
+ config->writeEntry("AutoStart", false);
+ } else // cancel chosen don't quit
+ return;
+ config->sync();
+
+ kapp->quit();
+
+}
+
+void KlipperWidget::slotPopupMenu() {
+ KlipperPopup* popup = history()->popup();
+ popup->ensureClean();
+ showPopupMenu( popup );
+}
+
+
+void KlipperWidget::slotRepeatAction()
+{
+ if ( !myURLGrabber ) {
+ myURLGrabber = new URLGrabber( m_config );
+ connect( myURLGrabber, SIGNAL( sigPopup( QPopupMenu * )),
+ SLOT( showPopupMenu( QPopupMenu * )) );
+ connect( myURLGrabber, SIGNAL( sigDisablePopup() ),
+ this, SLOT( disableURLGrabber() ) );
+ }
+
+ const HistoryStringItem* top = dynamic_cast<const HistoryStringItem*>( history()->first() );
+ if ( top ) {
+ myURLGrabber->invokeAction( top->text() );
+ }
+}
+
+void KlipperWidget::setURLGrabberEnabled( bool enable )
+{
+ if (enable != bURLGrabber) {
+ bURLGrabber = enable;
+ KConfig *kc = m_config;
+ kc->setGroup("General");
+ kc->writeEntry("URLGrabberEnabled", bURLGrabber);
+ m_lastURLGrabberTextSelection = QString();
+ m_lastURLGrabberTextClipboard = QString();
+ }
+
+ toggleURLGrabAction->setChecked( enable );
+
+ if ( !bURLGrabber ) {
+ delete myURLGrabber;
+ myURLGrabber = 0L;
+ toggleURLGrabAction->setText(i18n("Enable &Actions"));
+ }
+
+ else {
+ toggleURLGrabAction->setText(i18n("&Actions Enabled"));
+ if ( !myURLGrabber ) {
+ myURLGrabber = new URLGrabber( m_config );
+ connect( myURLGrabber, SIGNAL( sigPopup( QPopupMenu * )),
+ SLOT( showPopupMenu( QPopupMenu * )) );
+ connect( myURLGrabber, SIGNAL( sigDisablePopup() ),
+ this, SLOT( disableURLGrabber() ) );
+ }
+ }
+}
+
+void KlipperWidget::toggleURLGrabber()
+{
+ setURLGrabberEnabled( !bURLGrabber );
+}
+
+void KlipperWidget::slotHistoryTopChanged() {
+ if ( locklevel ) {
+ return;
+ }
+
+ const HistoryItem* topitem = history()->first();
+ if ( topitem ) {
+ setClipboard( *topitem, Clipboard | Selection );
+ }
+ if ( bReplayActionInHistory && bURLGrabber ) {
+ slotRepeatAction();
+ }
+
+}
+
+void KlipperWidget::slotClearClipboard()
+{
+ Ignore lock( locklevel );
+
+ clip->clear(QClipboard::Selection);
+ clip->clear(QClipboard::Clipboard);
+
+
+}
+
+
+//XXX: Should die, and the DCOP signal handled sensible.
+QString KlipperWidget::clipboardContents( bool * /*isSelection*/ )
+{
+ kdWarning() << "Obsolete function called. Please fix" << endl;
+
+#if 0
+ bool selection = true;
+ QMimeSource* data = clip->data(QClipboard::Selection);
+
+ if ( data->serialNumber() == m_lastSelection )
+ {
+ QString clipContents = clip->text(QClipboard::Clipboard);
+ if ( clipContents != m_lastClipboard )
+ {
+ contents = clipContents;
+ selection = false;
+ }
+ else
+ selection = true;
+ }
+
+ if ( isSelection )
+ *isSelection = selection;
+
+#endif
+
+ return 0;
+}
+
+void KlipperWidget::applyClipChanges( const QMimeSource& clipData )
+{
+ if ( locklevel )
+ return;
+ Ignore lock( locklevel );
+ history()->insert( HistoryItem::create( clipData ) );
+
+}
+
+void KlipperWidget::newClipData( bool selectionMode )
+{
+ if ( locklevel ) {
+ return;
+ }
+
+ if( blockFetchingNewData())
+ return;
+
+ checkClipData( selectionMode );
+
+}
+
+void KlipperWidget::clipboardSignalArrived( bool selectionMode )
+{
+ if ( locklevel ) {
+ return;
+ }
+ if( blockFetchingNewData())
+ return;
+
+ updateTimestamp();
+ checkClipData( selectionMode );
+}
+
+// Protection against too many clipboard data changes. Lyx responds to clipboard data
+// requests with setting new clipboard data, so if Lyx takes over clipboard,
+// Klipper notices, requests this data, this triggers "new" clipboard contents
+// from Lyx, so Klipper notices again, requests this data, ... you get the idea.
+const int MAX_CLIPBOARD_CHANGES = 10; // max changes per second
+
+bool KlipperWidget::blockFetchingNewData()
+{
+// Hacks for #85198 and #80302.
+// #85198 - block fetching new clipboard contents if Shift is pressed and mouse is not,
+// this may mean the user is doing selection using the keyboard, in which case
+// it's possible the app sets new clipboard contents after every change - Klipper's
+// history would list them all.
+// #80302 - OOo (v1.1.3 at least) has a bug that if Klipper requests its clipboard contents
+// while the user is doing a selection using the mouse, OOo stops updating the clipboard
+// contents, so in practice it's like the user has selected only the part which was
+// selected when Klipper asked first.
+ ButtonState buttonstate = kapp->keyboardMouseState();
+ if( ( buttonstate & ( ShiftButton | LeftButton )) == ShiftButton // #85198
+ || ( buttonstate & LeftButton ) == LeftButton ) { // #80302
+ m_pendingContentsCheck = true;
+ m_pendingCheckTimer.start( 100, true );
+ return true;
+ }
+ m_pendingContentsCheck = false;
+ if( ++m_overflowCounter > MAX_CLIPBOARD_CHANGES )
+ return true;
+ return false;
+}
+
+void KlipperWidget::slotCheckPending()
+{
+ if( !m_pendingContentsCheck )
+ return;
+ m_pendingContentsCheck = false; // blockFetchingNewData() will be called again
+ updateTimestamp();
+ newClipData( true ); // always selection
+}
+
+void KlipperWidget::checkClipData( bool selectionMode )
+{
+ if ( ignoreClipboardChanges() ) // internal to klipper, ignoring QSpinBox selections
+ {
+ // keep our old clipboard, thanks
+ // This won't quite work, but it's close enough for now.
+ // The trouble is that the top selection =! top clipboard
+ // but we don't track that yet. We will....
+ const HistoryItem* top = history()->first();
+ if ( top ) {
+ setClipboard( *top, selectionMode ? Selection : Clipboard);
+ }
+ return;
+ }
+
+// debug code
+
+// debug code
+#ifdef NOISY_KLIPPER
+ kdDebug() << "Checking clip data" << endl;
+#endif
+#if 0
+ kdDebug() << "====== c h e c k C l i p D a t a ============================"
+ << kdBacktrace()
+ << "====== c h e c k C l i p D a t a ============================"
+ << endl;;
+#endif
+#if 0
+ if ( sender() ) {
+ kdDebug() << "sender=" << sender()->name() << endl;
+ } else {
+ kdDebug() << "no sender" << endl;
+ }
+#endif
+#if 0
+ kdDebug() << "\nselectionMode=" << selectionMode
+ << "\nserialNo=" << clip->data()->serialNumber() << " (sel,cli)=(" << m_lastSelection << "," << m_lastClipboard << ")"
+ << "\nowning (sel,cli)=(" << clip->ownsSelection() << "," << clip->ownsClipboard() << ")"
+ << "\ntext=" << clip->text( selectionMode ? QClipboard::Selection : QClipboard::Clipboard) << endl;
+
+#endif
+#if 0
+ const char *format;
+ int i = 0;
+ while ( (format = clip->data()->format( i++ )) )
+ {
+ qDebug( " format: %s", format);
+ }
+#endif
+ QMimeSource* data = clip->data( selectionMode ? QClipboard::Selection : QClipboard::Clipboard );
+ if ( !data ) {
+ kdWarning("No data in clipboard. This not not supposed to happen." );
+ return;
+ }
+
+ int lastSerialNo = selectionMode ? m_lastSelection : m_lastClipboard;
+ bool changed = data->serialNumber() != lastSerialNo;
+ bool clipEmpty = ( data->format() == 0L );
+
+ if ( changed && clipEmpty && bNoNullClipboard ) {
+ const HistoryItem* top = history()->first();
+ if ( top ) {
+ // keep old clipboard after someone set it to null
+#ifdef NOISY_KLIPPER
+ kdDebug() << "Resetting clipboard (Prevent empty clipboard)" << endl;
+#endif
+ setClipboard( *top, selectionMode ? Selection : Clipboard );
+ }
+ return;
+ }
+
+ // this must be below the "bNoNullClipboard" handling code!
+ // XXX: I want a better handling of selection/clipboard in general.
+ // XXX: Order sensitive code. Must die.
+ if ( selectionMode && bIgnoreSelection )
+ return;
+
+ if( selectionMode && bSelectionTextOnly && !QTextDrag::canDecode( data ))
+ return;
+
+ if( KURLDrag::canDecode( data ))
+ ; // ok
+ else if( QTextDrag::canDecode( data ))
+ ; // ok
+ else if( QImageDrag::canDecode( data ))
+ {
+// Limit mimetypes that are tracked by Klipper (this is basically a workaround
+// for #109032). Can't add UI in 3.5 because of string freeze, and I'm not sure
+// if this actually needs to be more configurable than only text vs all klipper knows.
+ if( bIgnoreImages )
+ return;
+ else {
+ // ok
+ }
+ }
+ else // unknown, ignore
+ return;
+
+ // store old contents:
+ if ( selectionMode )
+ m_lastSelection = data->serialNumber();
+ else
+ m_lastClipboard = data->serialNumber();
+
+ QString& lastURLGrabberText = selectionMode
+ ? m_lastURLGrabberTextSelection : m_lastURLGrabberTextClipboard;
+ if( QTextDrag::canDecode( data ))
+ {
+ if ( bURLGrabber && myURLGrabber )
+ {
+ QString text;
+ QTextDrag::decode( data, text );
+ // 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 ( myURLGrabber->checkNewData( text ) )
+ {
+ return; // don't add into the history
+ }
+ }
+ }
+ else
+ lastURLGrabberText = QString();
+ }
+ else
+ lastURLGrabberText = QString();
+
+ if (changed) {
+ applyClipChanges( *data );
+#ifdef NOISY_KLIPPER
+ kdDebug() << "Synchronize?" << ( bSynchronize ? "yes" : "no" ) << endl;
+#endif
+ if ( bSynchronize ) {
+ const HistoryItem* topItem = history()->first();
+ if ( topItem ) {
+ setClipboard( *topItem, selectionMode ? Clipboard : Selection );
+ }
+ }
+ }
+
+}
+
+void KlipperWidget::setClipboard( const HistoryItem& item, int mode )
+{
+ Ignore lock( locklevel );
+
+ Q_ASSERT( ( mode & 1 ) == 0 ); // Warn if trying to pass a boolean as a mode.
+
+ if ( mode & Selection ) {
+#ifdef NOSIY_KLIPPER
+ kdDebug() << "Setting selection to <" << item.text() << ">" << endl;
+#endif
+ clip->setData( item.mimeSource(), QClipboard::Selection );
+ m_lastSelection = clip->data()->serialNumber();
+ }
+ if ( mode & Clipboard ) {
+#ifdef NOSIY_KLIPPER
+ kdDebug() << "Setting clipboard to <" << item.text() << ">" << endl;
+#endif
+ clip->setData( item.mimeSource(), QClipboard::Clipboard );
+ m_lastClipboard = clip->data()->serialNumber();
+ }
+
+}
+
+void KlipperWidget::slotClearOverflow()
+{
+ if( m_overflowCounter > MAX_CLIPBOARD_CHANGES ) {
+ kdDebug() << "App owning the clipboard/selection is lame" << endl;
+ // update to the latest data - this unfortunately may trigger the problem again
+ newClipData( true ); // Always the selection.
+ }
+ m_overflowCounter = 0;
+}
+
+QStringList KlipperWidget::getClipboardHistoryMenu()
+{
+ QStringList menu;
+ for ( const HistoryItem* item = history()->first(); item; item = history()->next() ) {
+ menu << item->text();
+ }
+ return menu;
+}
+
+QString KlipperWidget::getClipboardHistoryItem(int i)
+{
+ for ( const HistoryItem* item = history()->first(); item; item = history()->next() , i-- ) {
+ if ( i == 0 ) {
+ return item->text();
+ }
+ }
+ return QString::null;
+
+}
+
+//
+// changing a spinbox in klipper's config-dialog causes the lineedit-contents
+// of the spinbox to be selected and hence the clipboard changes. But we don't
+// want all those items in klipper's history. See #41917
+//
+bool KlipperWidget::ignoreClipboardChanges() const
+{
+ QWidget *focusWidget = qApp->focusWidget();
+ if ( focusWidget )
+ {
+ if ( focusWidget->inherits( "QSpinBox" ) ||
+ (focusWidget->parentWidget() &&
+ focusWidget->inherits("QLineEdit") &&
+ focusWidget->parentWidget()->inherits("QSpinWidget")) )
+ {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+// QClipboard uses qt_x_time as the timestamp for selection operations.
+// It is updated mainly from user actions, but Klipper polls the clipboard
+// without any user action triggering it, so qt_x_time may be old,
+// which could possibly lead to QClipboard reporting empty clipboard.
+// Therefore, qt_x_time needs to be updated to current X server timestamp.
+
+// Call KApplication::updateUserTime() only from functions that are
+// called from outside (DCOP), or from QTimer timeout !
+
+extern Time qt_x_time;
+extern Time qt_x_user_time;
+
+static Time next_x_time;
+static Bool update_x_time_predicate( Display*, XEvent* event, XPointer )
+{
+ if( next_x_time != CurrentTime )
+ return False;
+ // from qapplication_x11.cpp
+ switch ( event->type ) {
+ case ButtonPress:
+ // fallthrough intended
+ case ButtonRelease:
+ next_x_time = event->xbutton.time;
+ break;
+ case MotionNotify:
+ next_x_time = event->xmotion.time;
+ break;
+ case KeyPress:
+ // fallthrough intended
+ case KeyRelease:
+ next_x_time = event->xkey.time;
+ break;
+ case PropertyNotify:
+ next_x_time = event->xproperty.time;
+ break;
+ case EnterNotify:
+ case LeaveNotify:
+ next_x_time = event->xcrossing.time;
+ break;
+ case SelectionClear:
+ next_x_time = event->xselectionclear.time;
+ break;
+ default:
+ break;
+ }
+ return False;
+}
+
+void KlipperWidget::updateTimestamp()
+{ // Qt3.3.0 and 3.3.1 use qt_x_user_time for clipboard operations
+ Time& time = ( strcmp( qVersion(), "3.3.1" ) == 0
+ || strcmp( qVersion(), "3.3.0" ) == 0 )
+ ? qt_x_user_time : qt_x_time;
+ static QWidget* w = 0;
+ if ( !w )
+ w = new QWidget;
+ unsigned char data[ 1 ];
+ XChangeProperty( qt_xdisplay(), w->winId(), XA_ATOM, XA_ATOM, 8, PropModeAppend, data, 1 );
+ next_x_time = CurrentTime;
+ XEvent dummy;
+ XCheckIfEvent( qt_xdisplay(), &dummy, update_x_time_predicate, NULL );
+ if( next_x_time == CurrentTime )
+ {
+ XSync( qt_xdisplay(), False );
+ XCheckIfEvent( qt_xdisplay(), &dummy, update_x_time_predicate, NULL );
+ }
+ Q_ASSERT( next_x_time != CurrentTime );
+ time = next_x_time;
+ XEvent ev; // remove the PropertyNotify event from the events queue
+ XWindowEvent( qt_xdisplay(), w->winId(), PropertyChangeMask, &ev );
+}
+
+static const char * const description =
+ I18N_NOOP("KDE cut & paste history utility");
+
+void KlipperWidget::createAboutData()
+{
+ about_data = new KAboutData("klipper", I18N_NOOP("Klipper"),
+ klipper_version, description, KAboutData::License_GPL,
+ "(c) 1998, Andrew Stanley-Jones\n"
+ "1998-2002, Carsten Pfeiffer\n"
+ "2001, Patrick Dubroy");
+
+ about_data->addAuthor("Carsten Pfeiffer",
+ I18N_NOOP("Author"),
+
+ about_data->addAuthor("Andrew Stanley-Jones",
+ I18N_NOOP( "Original Author" ),
+
+ about_data->addAuthor("Patrick Dubroy",
+ I18N_NOOP("Contributor"),
+
+ about_data->addAuthor( "Luboš Luňák",
+ I18N_NOOP("Bugfixes and optimizations"),
+
+ about_data->addAuthor( "Esben Mose Hansen",
+ I18N_NOOP("Maintainer"),
+}
+
+void KlipperWidget::destroyAboutData()
+{
+ delete about_data;
+ about_data = NULL;
+}
+
+KAboutData* KlipperWidget::about_data;
+
+KAboutData* KlipperWidget::aboutData()
+{
+ return about_data;
+}
+
+Klipper::Klipper( QWidget* parent )
+ : KlipperWidget( parent, kapp->config())
+{
+}
+
+// this sucks ... KUniqueApplication registers itself as 'klipper'
+// for the unique-app detection calls (and it shouldn't use that name IMHO)
+// but in Klipper it's not KUniqueApplication class who handles
+// the DCOP calls, but an instance of class Klipper, registered as 'klipper'
+// this below avoids a warning when KUniqueApplication wouldn't otherwise
+// find newInstance() (which doesn't do anything in Klipper anyway)
+int Klipper::newInstance()
+{
+ kapp->dcopClient()->setPriorityCall(false); // Allow other dcop calls
+ return 0;
+}
+
+// this is used for quiting klipper process, if klipper is being started as an applet
+// (AKA ugly hack)
+void Klipper::quitProcess()
+{
+ kapp->dcopClient()->detach();
+ kapp->quit();
+}
+
+static void ensureGlobalSyncOff(KConfig* config) {
+ config->setGroup("General");
+ if ( config->readBoolEntry( "SynchronizeClipboardAndSelection" ) ) {
+ kdDebug() << "Shutting off global synchronization" << endl;
+ config->writeEntry("SynchronizeClipboardAndSelection",
+ false,
+ true,
+ true );
+ config->sync();
+ KClipboardSynchronizer::setSynchronizing( false );
+ KClipboardSynchronizer::setReverseSynchronizing( false );
+ KIPC::sendMessageAll( KIPC::ClipboardConfigChanged, 0 );
+ }
+
+}
+
+#include "toplevel.moc"
diff --git a/klipper/toplevel.h b/klipper/toplevel.h
new file mode 100644
index 000000000..735698487
--- /dev/null
+++ b/klipper/toplevel.h
@@ -0,0 +1,221 @@
+// -*- Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 8; -*-
+/* This file is part of the KDE project
+ Copyright (C) by Andrew Stanley-Jones
+ Copyright (C) 2004 Esben Mose Hansen <[email protected]>
+
+ 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; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+#ifndef _TOPLEVEL_H_
+#define _TOPLEVEL_H_
+
+#include <kapplication.h>
+#include <kglobalaccel.h>
+#include <kpopupmenu.h>
+#include <qmap.h>
+#include <qpixmap.h>
+#include <dcopobject.h>
+#include <qtimer.h>
+
+class QClipboard;
+class KToggleAction;
+class KAboutData;
+class URLGrabber;
+class ClipboardPoll;
+class QTime;
+class History;
+class KAction;
+class QMimeSource;
+class HistoryItem;
+class KlipperSessionManaged;
+
+class KlipperWidget : public QWidget, public DCOPObject
+{
+ Q_OBJECT
+ K_DCOP
+
+k_dcop:
+ QString getClipboardContents();
+ void setClipboardContents(QString s);
+ void clearClipboardContents();
+ void clearClipboardHistory();
+ QStringList getClipboardHistoryMenu();
+ QString getClipboardHistoryItem(int i);
+
+public:
+ KlipperWidget( QWidget *parent, KConfig* config );
+ ~KlipperWidget();
+
+ virtual void adjustSize();
+ KGlobalAccel *globalKeys;
+
+ /**
+ * Get clipboard history (the "document")
+ */
+ History* history() { return m_history; }
+
+ static void updateTimestamp();
+ static void createAboutData();
+ static void destroyAboutData();
+ static KAboutData* aboutData();
+
+public slots:
+ void saveSession();
+ void slotSettingsChanged( int category );
+ void slotHistoryTopChanged();
+ void slotConfigure();
+
+protected:
+ /**
+ * The selection modes
+ *
+ * Don't use 1, as I use that as a guard against passing
+ * a boolean true as a mode.
+ */
+ enum SelectionMode { Clipboard = 2, Selection = 4 };
+
+ void paintEvent(QPaintEvent *);
+ void mousePressEvent(QMouseEvent *);
+ void readProperties(KConfig *);
+ void readConfiguration(KConfig *);
+
+ /**
+ * Loads history from disk.
+ */
+ bool loadHistory();
+
+ /**
+ * Save history to disk
+ */
+ void saveHistory();
+
+ void writeConfiguration(KConfig *);
+ /**
+ * @returns the contents of the selection or, if empty, the contents of
+ * the clipboard.
+ */
+ QString clipboardContents( bool *isSelection = 0L );
+
+ void removeFromHistory( const QString& text );
+ void setEmptyClipboard();
+
+ void clipboardSignalArrived( bool selectionMode );
+
+ /**
+ * Check data in clipboard, and if it passes these checks,
+ * store the data in the clipboard history.
+ */
+ void checkClipData( bool selectionMode );
+
+ /**
+ * Enter clipboard data in the history.
+ */
+ void applyClipChanges( const QMimeSource& data );
+
+ void setClipboard( const HistoryItem& item, int mode );
+ bool ignoreClipboardChanges() const;
+
+ KConfig* config() const { return m_config; }
+ bool isApplet() const { return m_config != kapp->config(); }
+
+protected slots:
+ void slotPopupMenu();
+ void showPopupMenu( QPopupMenu * );
+ void slotRepeatAction();
+ void setURLGrabberEnabled( bool );
+ void toggleURLGrabber();
+ void disableURLGrabber();
+
+private slots:
+ void newClipData( bool selectionMode );
+ void slotClearClipboard();
+
+ void slotSelectionChanged() {
+ clipboardSignalArrived( true );
+ }
+ void slotClipboardChanged() {
+ clipboardSignalArrived( false );
+ }
+
+ void slotQuit();
+ void slotStartHideTimer();
+ void slotStartShowTimer();
+
+ void slotClearOverflow();
+ void slotCheckPending();
+
+private:
+
+ QClipboard *clip;
+
+ QTime *hideTimer;
+ QTime *showTimer;
+
+ QMimeSource* m_lastClipdata;
+ int m_lastClipboard;
+ int m_lastSelection;
+ History* m_history;
+ int m_overflowCounter;
+ KToggleAction *toggleURLGrabAction;
+ KAction* clearHistoryAction;
+ KAction* configureAction;
+ KAction* quitAction;
+ QPixmap m_pixmap;
+ bool bPopupAtMouse :1;
+ bool bKeepContents :1;
+ bool bURLGrabber :1;
+ bool bReplayActionInHistory :1;
+ bool bUseGUIRegExpEditor :1;
+ bool bNoNullClipboard :1;
+ bool bTearOffHandle :1;
+ bool bIgnoreSelection :1;
+ bool bSynchronize :1;
+ bool bSelectionTextOnly :1;
+ bool bIgnoreImages :1;
+
+ /**
+ * Avoid reacting to our own changes, using this
+ * lock.
+ * Don't manupulate this object directly... use the Ignore struct
+ * instead
+ */
+ int locklevel;
+
+ URLGrabber *myURLGrabber;
+ QString m_lastURLGrabberTextSelection;
+ QString m_lastURLGrabberTextClipboard;
+ KConfig* m_config;
+ QTimer m_overflowClearTimer;
+ QTimer m_pendingCheckTimer;
+ bool m_pendingContentsCheck;
+ ClipboardPoll* poll;
+ static KAboutData* about_data;
+
+ bool blockFetchingNewData();
+ KlipperSessionManaged* session_managed;
+};
+
+class Klipper : public KlipperWidget
+{
+ Q_OBJECT
+ K_DCOP
+k_dcop:
+ int newInstance();
+ void quitProcess(); // not ASYNC
+public:
+ Klipper( QWidget* parent = NULL );
+};
+
+#endif
diff --git a/klipper/urlgrabber.cpp b/klipper/urlgrabber.cpp
new file mode 100644
index 000000000..352f3ab74
--- /dev/null
+++ b/klipper/urlgrabber.cpp
@@ -0,0 +1,497 @@
+// -*- Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 8; -*-
+/* This file is part of the KDE project
+ Copyright (C) (C) 2000,2001,2002 by Carsten Pfeiffer <[email protected]>
+
+ 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; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#include <qcursor.h>
+#include <qtimer.h>
+
+#include <kapplication.h>
+#include <kconfig.h>
+#include <kdialogbase.h>
+#include <ktextedit.h>
+#include <klocale.h>
+#include <kpopupmenu.h>
+#include <kservice.h>
+#include <kiconloader.h>
+#include <kdebug.h>
+#include <netwm.h>
+#include <kstringhandler.h>
+#include <kmacroexpander.h>
+
+#include <stdlib.h> // For getenv()
+
+#include "urlgrabber.h"
+
+// TODO:
+// - script-interface?
+// - Bug in KPopupMenu::clear() (insertTitle() doesn't go away sometimes)
+
+#define URL_EDIT_ITEM 10
+#define DO_NOTHING_ITEM 11
+#define DISABLE_POPUP 12
+
+URLGrabber::URLGrabber( KConfig* config )
+ : m_config( config )
+{
+ if( m_config == NULL )
+ m_config = kapp->config();
+ myCurrentAction = 0L;
+ myMenu = 0L;
+ myPopupKillTimeout = 8;
+ m_stripWhiteSpace = true;
+
+ myActions = new ActionList();
+ myActions->setAutoDelete( true );
+ myMatches.setAutoDelete( false );
+
+ readConfiguration( m_config );
+
+ myPopupKillTimer = new QTimer( this );
+ connect( myPopupKillTimer, SIGNAL( timeout() ),
+ SLOT( slotKillPopupMenu() ));
+
+ // testing
+ /*
+ ClipAction *action;
+ action = new ClipAction( "^http:\\/\\/", "Web-URL" );
+ action->addCommand("kfmclient exec %s", "Open with Konqi", true);
+ action->addCommand("netscape -no-about-splash -remote \"openURL(%s, new-window)\"", "Open with Netscape", true);
+ myActions->append( action );
+
+ action = new ClipAction( "^mailto:", "Mail-URL" );
+ action->addCommand("kmail --composer %s", "Launch kmail", true);
+ myActions->append( action );
+
+ action = new ClipAction( "^\\/.+\\.jpg$", "Jpeg-Image" );
+ action->addCommand("kuickshow %s", "Launch KuickShow", true);
+ action->addCommand("kview %s", "Launch KView", true);
+ myActions->append( action );
+ */
+}
+
+
+URLGrabber::~URLGrabber()
+{
+ delete myActions;
+}
+
+//
+// Called from Klipper::slotRepeatAction, i.e. by pressing Ctrl-Alt-R
+// shortcut. I.e. never from clipboard monitoring
+//
+void URLGrabber::invokeAction( const QString& clip )
+{
+ if ( !clip.isEmpty() )
+ myClipData = clip;
+ if ( m_stripWhiteSpace )
+ myClipData = myClipData.stripWhiteSpace();
+
+ actionMenu( false );
+}
+
+
+void URLGrabber::setActionList( ActionList *list )
+{
+ delete myActions;
+ myActions = list;
+}
+
+
+const ActionList& URLGrabber::matchingActions( const QString& clipData )
+{
+ myMatches.clear();
+ ClipAction *action = 0L;
+ ActionListIterator it( *myActions );
+ for ( action = it.current(); action; action = ++it ) {
+ if ( action->matches( clipData ) )
+ myMatches.append( action );
+ }
+
+ return myMatches;
+}
+
+
+bool URLGrabber::checkNewData( const QString& clipData )
+{
+ // kdDebug() << "** checking new data: " << clipData << endl;
+ myClipData = clipData;
+ if ( m_stripWhiteSpace )
+ myClipData = myClipData.stripWhiteSpace();
+
+ if ( myActions->isEmpty() )
+ return false;
+
+ actionMenu( true ); // also creates myMatches
+
+ return ( !myMatches.isEmpty() &&
+ (!m_config->readBoolEntry("Put Matching URLs in history", true)));
+}
+
+
+void URLGrabber::actionMenu( bool wm_class_check )
+{
+ if ( myClipData.isEmpty() )
+ return;
+
+ ActionListIterator it( matchingActions( myClipData ) );
+ ClipAction *action = 0L;
+ ClipCommand *command = 0L;
+
+ if ( it.count() > 0 ) {
+ // don't react on konqi's/netscape's urls...
+ if ( wm_class_check && isAvoidedWindow() )
+ return;
+
+ QString item;
+ myCommandMapper.clear();
+
+ myPopupKillTimer->stop();
+ delete myMenu;
+ myMenu = new KPopupMenu;
+ connect( myMenu, SIGNAL( activated( int )),
+ SLOT( slotItemSelected( int )));
+
+ for ( action = it.current(); action; action = ++it ) {
+ QPtrListIterator<ClipCommand> it2( action->commands() );
+ if ( it2.count() > 0 )
+ myMenu->insertTitle( SmallIcon( "klipper" ), action->description() +
+ i18n(" - Actions For: ") +
+ KStringHandler::csqueeze(myClipData, 45));
+ for ( command = it2.current(); command; command = ++it2 ) {
+ item = command->description;
+ if ( item.isEmpty() )
+ item = command->command;
+
+ int id;
+ if ( command->pixmap.isEmpty() )
+ id = myMenu->insertItem( item );
+ else
+ id = myMenu->insertItem( SmallIcon(command->pixmap), item);
+ myCommandMapper.insert( id, command );
+ }
+ }
+
+ // only insert this when invoked via clipboard monitoring, not from an
+ // explicit Ctrl-Alt-R
+ if ( wm_class_check )
+ {
+ myMenu->insertSeparator();
+ myMenu->insertItem( i18n( "Disable This Popup" ), DISABLE_POPUP );
+ }
+ myMenu->insertSeparator();
+ // add an edit-possibility
+ myMenu->insertItem( SmallIcon("edit"), i18n("&Edit Contents..."),
+ URL_EDIT_ITEM );
+ myMenu->insertItem( SmallIconSet("cancel"), i18n("&Cancel"), DO_NOTHING_ITEM );
+
+ if ( myPopupKillTimeout > 0 )
+ myPopupKillTimer->start( 1000 * myPopupKillTimeout, true );
+
+ emit sigPopup( myMenu );
+ }
+}
+
+
+void URLGrabber::slotItemSelected( int id )
+{
+ myMenu->hide(); // deleted by the timer or the next action
+
+ switch ( id ) {
+ case -1:
+ case DO_NOTHING_ITEM:
+ break;
+ case URL_EDIT_ITEM:
+ editData();
+ break;
+ case DISABLE_POPUP:
+ emit sigDisablePopup();
+ break;
+ default:
+ ClipCommand *command = myCommandMapper.find( id );
+ if ( !command )
+ qWarning("Klipper: can't find associated action");
+ else
+ execute( command );
+ }
+}
+
+
+void URLGrabber::execute( const struct ClipCommand *command ) const
+{
+ if ( command->isEnabled ) {
+ QMap<QChar,QString> map;
+ map.insert( 's', myClipData );
+ QString cmdLine = KMacroExpander::expandMacrosShellQuote( command->command, map );
+
+ if ( cmdLine.isEmpty() )
+ return;
+
+ KProcess proc;
+ const char *shell = getenv("KLIPPER_SHELL");
+ if (shell==NULL) shell = getenv("SHELL");
+ proc.setUseShell(true,shell);
+
+ proc << cmdLine.stripWhiteSpace();
+
+ if ( !proc.start(KProcess::DontCare, KProcess::NoCommunication ))
+ qWarning("Klipper: Couldn't start process!");
+ }
+}
+
+
+void URLGrabber::editData()
+{
+ myPopupKillTimer->stop();
+ KDialogBase *dlg = new KDialogBase( 0, 0, true,
+ i18n("Edit Contents"),
+ KDialogBase::Ok | KDialogBase::Cancel);
+ KTextEdit *edit = new KTextEdit( dlg );
+ edit->setText( myClipData );
+ edit->setFocus();
+ edit->setMinimumSize( 300, 40 );
+ dlg->setMainWidget( edit );
+ dlg->adjustSize();
+
+ if ( dlg->exec() == QDialog::Accepted ) {
+ myClipData = edit->text();
+ delete dlg;
+ QTimer::singleShot( 0, this, SLOT( slotActionMenu() ) );
+ }
+ else
+ {
+ delete dlg;
+ myMenu->deleteLater();
+ myMenu = 0L;
+ }
+}
+
+
+void URLGrabber::readConfiguration( KConfig *kc )
+{
+ myActions->clear();
+ kc->setGroup( "General" );
+ int num = kc->readNumEntry("Number of Actions", 0);
+ myAvoidWindows = kc->readListEntry("No Actions for WM_CLASS");
+ myPopupKillTimeout = kc->readNumEntry( "Timeout for Action popups (seconds)", 8 );
+ m_stripWhiteSpace = kc->readBoolEntry("Strip Whitespace before exec", true);
+ QString group;
+ for ( int i = 0; i < num; i++ ) {
+ group = QString("Action_%1").arg( i );
+ kc->setGroup( group );
+ myActions->append( new ClipAction( kc ) );
+ }
+}
+
+
+void URLGrabber::writeConfiguration( KConfig *kc )
+{
+ kc->setGroup( "General" );
+ kc->writeEntry( "Number of Actions", myActions->count() );
+ kc->writeEntry( "Timeout for Action popups (seconds)", myPopupKillTimeout);
+ kc->writeEntry( "No Actions for WM_CLASS", myAvoidWindows );
+ kc->writeEntry( "Strip Whitespace before exec", m_stripWhiteSpace );
+
+ ActionListIterator it( *myActions );
+ ClipAction *action;
+
+ int i = 0;
+ QString group;
+ while ( (action = it.current()) ) {
+ group = QString("Action_%1").arg( i );
+ kc->setGroup( group );
+ action->save( kc );
+ ++i;
+ ++it;
+ }
+}
+
+// find out whether the active window's WM_CLASS is in our avoid-list
+// digged a little bit in netwm.cpp
+bool URLGrabber::isAvoidedWindow() const
+{
+ Display *d = qt_xdisplay();
+ static Atom wm_class = XInternAtom( d, "WM_CLASS", true );
+ static Atom active_window = XInternAtom( d, "_NET_ACTIVE_WINDOW", true );
+ Atom type_ret;
+ int format_ret;
+ unsigned long nitems_ret, unused;
+ unsigned char *data_ret;
+ long BUFSIZE = 2048;
+ bool ret = false;
+ Window active = 0L;
+ QString wmClass;
+
+ // get the active window
+ if (XGetWindowProperty(d, DefaultRootWindow( d ), active_window, 0l, 1l,
+ False, XA_WINDOW, &type_ret, &format_ret,
+ &nitems_ret, &unused, &data_ret)
+ == Success) {
+ if (type_ret == XA_WINDOW && format_ret == 32 && nitems_ret == 1) {
+ active = *((Window *) data_ret);
+ }
+ XFree(data_ret);
+ }
+ if ( !active )
+ return false;
+
+ // get the class of the active window
+ if ( XGetWindowProperty(d, active, wm_class, 0L, BUFSIZE, False, XA_STRING,
+ &type_ret, &format_ret, &nitems_ret,
+ &unused, &data_ret ) == Success) {
+ if ( type_ret == XA_STRING && format_ret == 8 && nitems_ret > 0 ) {
+ wmClass = QString::fromUtf8( (const char *) data_ret );
+ ret = (myAvoidWindows.find( wmClass ) != myAvoidWindows.end());
+ }
+
+ XFree( data_ret );
+ }
+
+ return ret;
+}
+
+
+void URLGrabber::slotKillPopupMenu()
+{
+ if ( myMenu && myMenu->isVisible() )
+ {
+ if ( myMenu->geometry().contains( QCursor::pos() ) &&
+ myPopupKillTimeout > 0 )
+ {
+ myPopupKillTimer->start( 1000 * myPopupKillTimeout, true );
+ return;
+ }
+ }
+
+ delete myMenu;
+ myMenu = 0L;
+}
+
+///////////////////////////////////////////////////////////////////////////
+////////
+
+ClipCommand::ClipCommand(const QString &_command, const QString &_description,
+ bool _isEnabled, const QString &_icon)
+ : command(_command),
+ description(_description),
+ isEnabled(_isEnabled)
+{
+ int len = command.find(" ");
+ if (len == -1)
+ len = command.length();
+
+ if (!_icon.isEmpty())
+ pixmap = _icon;
+ else
+ {
+ KService::Ptr service= KService::serviceByDesktopName(command.left(len));
+ if (service)
+ pixmap = service->icon();
+ else
+ pixmap = QString::null;
+ }
+}
+
+
+ClipAction::ClipAction( const QString& regExp, const QString& description )
+ : myRegExp( regExp ), myDescription( description )
+{
+ myCommands.setAutoDelete( true );
+}
+
+
+ClipAction::ClipAction( const ClipAction& action )
+{
+ myCommands.setAutoDelete( true );
+ myRegExp = action.myRegExp;
+ myDescription = action.myDescription;
+
+ ClipCommand *command = 0L;
+ QPtrListIterator<ClipCommand> it( myCommands );
+ for ( ; it.current(); ++it ) {
+ command = it.current();
+ addCommand(command->command, command->description, command->isEnabled);
+ }
+}
+
+
+ClipAction::ClipAction( KConfig *kc )
+ : myRegExp( kc->readEntry( "Regexp" ) ),
+ myDescription( kc->readEntry( "Description" ) )
+{
+ myCommands.setAutoDelete( true );
+ int num = kc->readNumEntry( "Number of commands" );
+
+ // read the commands
+ QString actionGroup = kc->group();
+ for ( int i = 0; i < num; i++ ) {
+ QString group = actionGroup + "/Command_%1";
+ kc->setGroup( group.arg( i ) );
+
+ addCommand( kc->readPathEntry( "Commandline" ),
+ kc->readEntry( "Description" ), // i18n'ed
+ kc->readBoolEntry( "Enabled" ),
+ kc->readEntry( "Icon") );
+ }
+}
+
+
+ClipAction::~ClipAction()
+{
+}
+
+
+void ClipAction::addCommand( const QString& command,
+ const QString& description, bool enabled, const QString& icon )
+{
+ if ( command.isEmpty() )
+ return;
+
+ struct ClipCommand *cmd = new ClipCommand( command, description, enabled, icon );
+ // cmd->id = myCommands.count(); // superfluous, I think...
+ myCommands.append( cmd );
+}
+
+
+// precondition: we're in the correct action's group of the KConfig object
+void ClipAction::save( KConfig *kc ) const
+{
+ kc->writeEntry( "Description", description() );
+ kc->writeEntry( "Regexp", regExp() );
+ kc->writeEntry( "Number of commands", myCommands.count() );
+
+ QString actionGroup = kc->group();
+ struct ClipCommand *cmd;
+ QPtrListIterator<struct ClipCommand> it( myCommands );
+
+ // now iterate over all commands of this action
+ int i = 0;
+ while ( (cmd = it.current()) ) {
+ QString group = actionGroup + "/Command_%1";
+ kc->setGroup( group.arg( i ) );
+
+ kc->writePathEntry( "Commandline", cmd->command );
+ kc->writeEntry( "Description", cmd->description );
+ kc->writeEntry( "Enabled", cmd->isEnabled );
+
+ ++i;
+ ++it;
+ }
+}
+
+#include "urlgrabber.moc"
diff --git a/klipper/urlgrabber.h b/klipper/urlgrabber.h
new file mode 100644
index 000000000..ae39d648d
--- /dev/null
+++ b/klipper/urlgrabber.h
@@ -0,0 +1,159 @@
+// -*- Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 8; -*-
+/* This file is part of the KDE project
+ Copyright (C) 2000 by Carsten Pfeiffer <[email protected]>
+
+ 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; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+#ifndef URLGRABBER_H
+#define URLGRABBER_H
+
+#include <qptrlist.h>
+#include <qintdict.h>
+#include <qregexp.h>
+#include <qstring.h>
+#include <qstringlist.h>
+
+#include <kprocess.h>
+
+
+class QTimer;
+
+class KConfig;
+class KPopupMenu;
+
+class ClipAction;
+struct ClipCommand;
+typedef QPtrList<ClipAction> ActionList;
+typedef QPtrListIterator<ClipAction> ActionListIterator;
+
+class URLGrabber : public QObject
+{
+ Q_OBJECT
+
+public:
+ URLGrabber( KConfig* config );
+ ~URLGrabber();
+
+ /**
+ * Checks a given string whether it matches any of the user-defined criteria.
+ * If it does, the configured action will be executed.
+ * @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::null );
+
+ const ActionList * actionList() const { return myActions; }
+ void setActionList( ActionList * );
+ void readConfiguration( KConfig * );
+ void writeConfiguration( KConfig * );
+
+ int popupTimeout() const { return myPopupKillTimeout; }
+ void setPopupTimeout( int timeout ) { myPopupKillTimeout = timeout; }
+
+ const QStringList& avoidWindows() const { return myAvoidWindows; }
+ void setAvoidWindows( const QStringList& list ) { myAvoidWindows = list; }
+
+ bool stripWhiteSpace() const { return m_stripWhiteSpace; }
+ void setStripWhiteSpace( bool enable ) { m_stripWhiteSpace = enable; }
+
+private:
+ const ActionList& matchingActions( const QString& );
+ void execute( const struct ClipCommand *command ) const;
+ void editData();
+ bool isAvoidedWindow() const;
+ void actionMenu( bool wm_class_check );
+
+ ActionList *myActions;
+ ActionList myMatches;
+ QStringList myAvoidWindows;
+ QString myClipData;
+ ClipAction *myCurrentAction;
+ QIntDict<ClipCommand> myCommandMapper;
+ KPopupMenu *myMenu;
+ QTimer *myPopupKillTimer;
+ int myPopupKillTimeout;
+ bool m_stripWhiteSpace;
+ KConfig* m_config;
+
+private slots:
+ void slotActionMenu() { actionMenu( true ); }
+ void slotItemSelected( int );
+ void slotKillPopupMenu();
+
+
+signals:
+ void sigPopup( QPopupMenu * );
+ void sigDisablePopup();
+
+};
+
+
+struct ClipCommand
+{
+ ClipCommand( const QString &, const QString &, bool = true, const QString & = "" );
+ QString command;
+ QString description;
+ bool isEnabled;
+ QString pixmap;
+ // int id; // the index reflecting the position in the list of commands
+};
+
+/**
+ * Represents one configured action. An action consists of one regular
+ * expression, an (optional) description and a list of ClipCommands
+ * (a command to be executed, a description and an enabled/disabled flag).
+ */
+class ClipAction
+{
+public:
+ ClipAction( const QString& regExp, const QString& description );
+ ClipAction( const ClipAction& );
+ ClipAction( KConfig *kc );
+ ~ClipAction();
+
+ void setRegExp( const QString& r) { myRegExp = QRegExp( r ); }
+ QString regExp() const { return myRegExp.pattern(); }
+ inline bool matches( const QString& string ) const {
+ return ( myRegExp.search( string ) != -1 );
+ }
+
+ void setDescription( const QString& d) { myDescription = d; }
+ const QString& description() const { return myDescription; }
+
+ /**
+ * Removes all ClipCommands associated with this ClipAction.
+ */
+ void clearCommands() { myCommands.clear(); }
+
+ void addCommand( const QString& command, const QString& description, bool, const QString& icon = "" );
+ const QPtrList<ClipCommand>& commands() const { return myCommands; }
+
+ /**
+ * Saves this action to a a given KConfig object
+ */
+ void save( KConfig * ) const;
+
+
+private:
+ QRegExp myRegExp;
+ QString myDescription;
+ QPtrList<ClipCommand> myCommands;
+
+};
+
+
+#endif // URLGRABBER_H
diff --git a/klipper/version.h b/klipper/version.h
new file mode 100644
index 000000000..758d59a6d
--- /dev/null
+++ b/klipper/version.h
@@ -0,0 +1,26 @@
+// -*- Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 8; -*-
+/* This file is part of the KDE project
+ Copyright (C) Andrew Stanley-Jones
+
+ 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; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+#ifndef _VERSION_H_
+#define _VERSION_H_
+
+static const char * const klipper_version = "v0.9.7";
+
+
+#endif