From 0495b46d1617762ed8749305b201c44747e251be Mon Sep 17 00:00:00 2001 From: David Rosca Date: Sat, 3 Mar 2018 12:40:54 +0100 Subject: [PATCH] Add RunAction extension Allows to run actions on sites. Actions are specified in desktop files and allow to either load url or run command depending on defined conditions. This extension is a generic replacement for ImageFinder and Videoner plugins. --- src/scripts/CMakeLists.txt | 1 + src/scripts/Messages.sh | 9 +- src/scripts/runaction/__init__.py | 18 +++ src/scripts/runaction/action.py | 73 +++++++++++ src/scripts/runaction/actionmanager.py | 113 ++++++++++++++++++ .../runaction/actions/dictionary.desktop | 8 ++ src/scripts/runaction/actions/google.png | Bin 0 -> 1025 bytes .../actions/googleimagesearch.desktop | 11 ++ src/scripts/runaction/actions/mpv.desktop | 10 ++ src/scripts/runaction/actions/tineye.png | Bin 0 -> 2569 bytes .../actions/tineyeimagesearch.desktop | 11 ++ .../runaction/actions/translate.desktop | 9 ++ src/scripts/runaction/actions/translate.png | Bin 0 -> 1443 bytes src/scripts/runaction/actions/w3.desktop | 9 ++ src/scripts/runaction/actions/w3.png | Bin 0 -> 1061 bytes src/scripts/runaction/actions/yandex.png | Bin 0 -> 669 bytes .../actions/yandeximagesearch.desktop | 11 ++ src/scripts/runaction/button.py | 50 ++++++++ src/scripts/runaction/icon.svg | 13 ++ src/scripts/runaction/metadata.desktop | 11 ++ src/scripts/runaction/runaction.py | 70 +++++++++++ src/scripts/runaction/settings.ui | 47 ++++++++ src/scripts/runaction/settingsdialog.py | 58 +++++++++ 23 files changed, 531 insertions(+), 1 deletion(-) create mode 100644 src/scripts/runaction/__init__.py create mode 100644 src/scripts/runaction/action.py create mode 100644 src/scripts/runaction/actionmanager.py create mode 100644 src/scripts/runaction/actions/dictionary.desktop create mode 100644 src/scripts/runaction/actions/google.png create mode 100644 src/scripts/runaction/actions/googleimagesearch.desktop create mode 100644 src/scripts/runaction/actions/mpv.desktop create mode 100644 src/scripts/runaction/actions/tineye.png create mode 100644 src/scripts/runaction/actions/tineyeimagesearch.desktop create mode 100644 src/scripts/runaction/actions/translate.desktop create mode 100644 src/scripts/runaction/actions/translate.png create mode 100644 src/scripts/runaction/actions/w3.desktop create mode 100644 src/scripts/runaction/actions/w3.png create mode 100644 src/scripts/runaction/actions/yandex.png create mode 100644 src/scripts/runaction/actions/yandeximagesearch.desktop create mode 100644 src/scripts/runaction/button.py create mode 100644 src/scripts/runaction/icon.svg create mode 100644 src/scripts/runaction/metadata.desktop create mode 100644 src/scripts/runaction/runaction.py create mode 100644 src/scripts/runaction/settings.ui create mode 100644 src/scripts/runaction/settingsdialog.py diff --git a/src/scripts/CMakeLists.txt b/src/scripts/CMakeLists.txt index 4f9526c83..d7eda83b7 100644 --- a/src/scripts/CMakeLists.txt +++ b/src/scripts/CMakeLists.txt @@ -6,3 +6,4 @@ function(install_python_script name) endfunction() install_python_script(hellopython) +install_python_script(runaction) diff --git a/src/scripts/Messages.sh b/src/scripts/Messages.sh index 38a0dcb6c..17f258b72 100644 --- a/src/scripts/Messages.sh +++ b/src/scripts/Messages.sh @@ -8,4 +8,11 @@ XGETTEXT_FLAGS_PYTHON="\ -ki18n:1 -ki18np:1,2 \ " -$XGETTEXT_PROGRAM $XGETTEXT_FLAGS_PYTHON `find hellopython -name '*.py'` -o $podir/falkon_hellopython.pot +python_scripts=" +hellopython +runaction +" + +for script in $python_scripts; do + $XGETTEXT_PROGRAM $XGETTEXT_FLAGS_PYTHON `find $script -name '*.py'` -o $podir/falkon_$script.pot +done diff --git a/src/scripts/runaction/__init__.py b/src/scripts/runaction/__init__.py new file mode 100644 index 000000000..1949bc144 --- /dev/null +++ b/src/scripts/runaction/__init__.py @@ -0,0 +1,18 @@ +# ============================================================ +# RunAction plugin for Falkon +# Copyright (C) 2018 David Rosca +# +# 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 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# ============================================================ +from .runaction import * diff --git a/src/scripts/runaction/action.py b/src/scripts/runaction/action.py new file mode 100644 index 000000000..e76dae80d --- /dev/null +++ b/src/scripts/runaction/action.py @@ -0,0 +1,73 @@ +# ============================================================ +# RunAction plugin for Falkon +# Copyright (C) 2018 David Rosca +# +# 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 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# ============================================================ +import Falkon +import os, re, enum, shlex +from PySide2 import QtCore, QtGui + +class Action(): + class Type(enum.Enum): + Invalid, Url, Command = range(3) + + class TypeCondition(enum.Enum): + Page, Link, Image, Media, Text = range(5) + + id = "" + title = "" + menuTitle = "" + icon = QtGui.QIcon() + actionType = Type.Invalid + typeCondition = [ TypeCondition.Page, TypeCondition.Link, TypeCondition.Image, TypeCondition.Media ] + urlCondition = ".*" + submenu = "" + normalExec = "" + textExec = "" + supported = False + + def __init__(self, fileName): + data = Falkon.DesktopFile(fileName) + self.id = os.path.splitext(os.path.basename(fileName))[0] + self.title = data.name() + self.menuTitle = data.comment() + self.icon = QtGui.QIcon.fromTheme(data.icon(), QtGui.QIcon(os.path.join(os.path.dirname(fileName), data.icon()))) + self.actionType = Action.Type[data.value("X-RunAction-Type")] + self.typeCondition = list(map(lambda s: Action.TypeCondition[s], data.value("X-RunAction-TypeCondition").split(";"))) + self.urlCondition = data.value("X-RunAction-UrlCondition") or self.urlCondition + self.submenu = data.value("X-RunAction-Submenu") or self.submenu + self.normalExec = data.value("X-RunAction-Exec") or self.normalExec + self.textExec = data.value("X-RunAction-TextExec") or self.normalExec + self.supported = data.tryExec() + + def testAction(self, condition, url): + if not self.supported: return False + if not condition in self.typeCondition: return False + if not re.match(self.urlCondition, url.toString()): return False + return True + + def execAction(self, url, text=""): + url = str(url.toEncoded()) + if self.actionType == Action.Type.Command: + url = shlex.quote(url) + text = shlex.quote(text) + elif self.actionType == Action.Type.Url: + url = str(QtCore.QUrl.toPercentEncoding(url)) + text = str(QtCore.QUrl.toPercentEncoding(text)) + command = self.normalExec if text == "" else self.textExec + command = command.replace("{url}", url) + command = command.replace("{text}", text) + command = command.replace("{lang}", QtCore.QLocale.system().name()[:2]) + return command diff --git a/src/scripts/runaction/actionmanager.py b/src/scripts/runaction/actionmanager.py new file mode 100644 index 000000000..d1bd92bb5 --- /dev/null +++ b/src/scripts/runaction/actionmanager.py @@ -0,0 +1,113 @@ +# ============================================================ +# RunAction plugin for Falkon +# Copyright (C) 2018 David Rosca +# +# 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 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# ============================================================ +import Falkon +import os, subprocess +from PySide2 import QtCore, QtGui, QtWidgets, QtUiTools +from runaction.action import Action +from runaction.settingsdialog import SettingsDialog + +class ActionManager(QtCore.QObject): + actions = [] + + def __init__(self, settingsPath, parent=None): + super().__init__(parent) + + self.settingsPath = settingsPath + settings = QtCore.QSettings(self.settingsPath + "/extensions.ini", QtCore.QSettings.IniFormat) + self._disabledActions = settings.value("RunAction/disabledActions") or [] + self.loadActions() + + def getActions(self, webView, r=None): + out = [] + menus = {} + + for action in list(filter(lambda a: a.id not in self.disabledActions, self.actions)): + url = webView.url() + text = "" + if r and webView.selectedText(): + cond = Action.TypeCondition.Text + text = webView.selectedText() + elif r and not r.linkUrl().isEmpty(): + cond = Action.TypeCondition.Link + url = r.linkUrl() + elif r and not r.imageUrl().isEmpty(): + cond = Action.TypeCondition.Image + url = r.imageUrl() + elif r and not r.mediaUrl().isEmpty(): + cond = Action.TypeCondition.Media + url = r.mediaUrl() + else: + cond = Action.TypeCondition.Page + + if action.testAction(cond, url): + act = Falkon.Action(action.icon, action.title, self) + act.triggered.connect(lambda a=action, w=webView, u=url, t=text: self.execAction(a, w, u, t)) + if action.submenu: + if not action.submenu in menus: + menu = Falkon.Menu(action.menuTitle, webView) + menus[action.submenu] = menu + out.append(menu) + menus[action.submenu].addAction(act) + else: + out.append(act) + + return out + + @property + def disabledActions(self): + return self._disabledActions + + @disabledActions.setter + def disabledActions(self, value): + settings = QtCore.QSettings(self.settingsPath + "/extensions.ini", QtCore.QSettings.IniFormat) + settings.setValue("RunAction/disabledActions", value) + self._disabledActions = value + + def showSettings(self, parent=None): + dialog = SettingsDialog(self, parent) + dialog.exec_() + + def execAction(self, action, webView, url, text=""): + command = action.execAction(url, text) + if action.actionType == Action.Type.Command: + subprocess.Popen(command, shell=True) + elif action.actionType == Action.Type.Url: + webView.openUrlInNewTab(QtCore.QUrl(command), Falkon.Qz.NT_SelectedTab) + + def loadActions(self): + self.actions = [] + + paths = [ + os.path.join(os.path.dirname(__file__), "actions"), + os.path.join(self.settingsPath, "runaction") + ] + + for path in paths: + if not os.path.exists(path): + continue + for file in os.listdir(path): + if not file.endswith(".desktop"): + continue + fileName = os.path.join(path, file) + try: + action = Action(fileName) + except Exception as e: + print("Failed to parse {}: {}".format(fileName, e)) + finally: + if action.supported: + self.actions.append(action) diff --git a/src/scripts/runaction/actions/dictionary.desktop b/src/scripts/runaction/actions/dictionary.desktop new file mode 100644 index 000000000..140b059f3 --- /dev/null +++ b/src/scripts/runaction/actions/dictionary.desktop @@ -0,0 +1,8 @@ +[Desktop Entry] +Name=Dictionary + +Type=Service + +X-RunAction-Type="Url" +X-RunAction-TypeCondition="Text" +X-RunAction-Exec="http://{lang}.wiktionary.org/wiki/Special:Search?search={text}" diff --git a/src/scripts/runaction/actions/google.png b/src/scripts/runaction/actions/google.png new file mode 100644 index 0000000000000000000000000000000000000000..475d168dd7ac60ec5c5438b94bbc5fdb32438809 GIT binary patch literal 1025 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 literal 0 HcmV?d00001 diff --git a/src/scripts/runaction/actions/tineyeimagesearch.desktop b/src/scripts/runaction/actions/tineyeimagesearch.desktop new file mode 100644 index 000000000..10c1485a2 --- /dev/null +++ b/src/scripts/runaction/actions/tineyeimagesearch.desktop @@ -0,0 +1,11 @@ +[Desktop Entry] +Name=TinEye +Comment=Search with... + +Icon=tineye.png +Type=Service + +X-RunAction-Type="Url" +X-RunAction-TypeCondition="Image" +X-RunAction-Submenu="search_with" +X-RunAction-Exec="http://www.tineye.com/search?url={url}" diff --git a/src/scripts/runaction/actions/translate.desktop b/src/scripts/runaction/actions/translate.desktop new file mode 100644 index 000000000..c23a1a2e2 --- /dev/null +++ b/src/scripts/runaction/actions/translate.desktop @@ -0,0 +1,9 @@ +[Desktop Entry] +Name=Translate page + +Icon=translate.png +Type=Service + +X-RunAction-Type="Url" +X-RunAction-TypeCondition="Page" +X-RunAction-Exec="http://translate.google.com/translate?sl=auto&tl={lang}&u={url}" diff --git a/src/scripts/runaction/actions/translate.png b/src/scripts/runaction/actions/translate.png new file mode 100644 index 0000000000000000000000000000000000000000..4583aa782e4d171d29e9710835c591e9466c71a7 GIT binary patch literal 1443 zcmV;U1zh@xP)}W|y!bPwCeV;cu$vGqeYrOcKbGXU>_df6QKIb4D(NDjtXj?Q@ z>SN&OVyH+_oJz9uyNn8blJXP^5#2J%QfzNq#x}<0 zmT0W9v-2&6PzVBFwIn=CK$lx!YikTURKX7zLLmse-1^m5uaJbN1+;N&KQrIV5DG!y z`z#607rs(}C{_Qn*wBDC8N#cQC7@VCKn&leWmRZl%~(79K0|m_1O;_@p5WD1&jniJ zICME+H4fOm_ILt^yxvf?-%kV&_1x8~o7v~{`HBF%As5iS zd^i_iROpsq7_h)Q_g0Ev?JA(c^(-!hv9D77Ggtc=qfWzY`=QMHC~L#J@rr4HwC3iC_9- zh(sb-WY}k-urd}GBM2=-u0P!qWoV*;p0#n4nUVz_IpB_{x2khL@)JxqA|$ ztO_>UBwQ{x-{;jXwQA&s4O_R5S7tuS;`WqzaEBZgan*$I^FoZUWG4CwXH3% zjR{CsnikMzRz2>EVtgtJ+jO!zR3&M6Y0ENp=%W}iO~8`chP`!4?5)?xDv?`&&2(v) zDYXS(w#Q{9C+cn``{Rx&CJQG@NTT*9>^h;u?r&6_y>)6<2{o&PmZ9TRv8oVsBy$R! zzMX19R)xc?@x<_bR}=7rl5#aS5?SHI@he{3?tYB!UIXqwHsGOQ5D$$*Xz%Jl)k!V7 zdi!7;F~eXoX9Vc2wsplyI}FKVNH_ygI8w)u{=R99ZnuXG zn<;31=)h6sc@`Lw4W}#tv9XAgzs51-isJ9*4AvK;o+UJQ#q)-5)+9KWZVlnn<4RoV z=;EiLZ_vcIqT`PqOixeqo{$M_VJ{SEu$0o3w5%S!iP3l?fTj!W*i)-U?~{JiXfB|p zyG;0+0Ya<7As>A*^rMj za*EF^F^RY=m?&mcU3!iQ>m++GIN|Xm2NL1+dgTJ_^2(ez^;daOMK_8n?qw)XqB>{f z<)=5JC_wkuDnS1kZQOYmMU_1)JVSkRU-H|)p8Pg&yORX-Hs~g88=bVc(-CBQUY| xyxDBQ#DtA~no$KDT!N|~B5ntTYc!qj{Rbs!gD+{TAbJ1*002ovPDHLkV1h+Fqs0IK literal 0 HcmV?d00001 diff --git a/src/scripts/runaction/actions/w3.desktop b/src/scripts/runaction/actions/w3.desktop new file mode 100644 index 000000000..9855507e6 --- /dev/null +++ b/src/scripts/runaction/actions/w3.desktop @@ -0,0 +1,9 @@ +[Desktop Entry] +Name=Validate page + +Icon=w3.png +Type=Service + +X-RunAction-Type="Url" +X-RunAction-TypeCondition="Page" +X-RunAction-Exec="http://validator.w3.org/check?uri={url}" diff --git a/src/scripts/runaction/actions/w3.png b/src/scripts/runaction/actions/w3.png new file mode 100644 index 0000000000000000000000000000000000000000..2fa751995b8e50fef8be07297e1dc3c95ee296ad GIT binary patch literal 1061 zcmV+=1ls$FP)!f}H_bMuVPZ)xkWsC|K+n4a)Yv zl7`yV{orsij%=7~+pfKl25>I?JG9>}HKPn*oJhw==`LYV`rhKG!C%Kw-jyn7| z+#CncJ1_!EJY#6!;RpFf3tWjU*DVkQzk3Enx`AO0EM@LTJp$2}jEWYp-5*Z6p!6E^ zL3ki2j|MuNh=snPQPDsjc^UUmx}sU6EB(^Jb@3&QyuZ7Dm;+nood|;?8uI|;1@+ue zaa{-RAJu4K^|>UjJN2wYWaN56CH31MilAT=#4o0t&v{keY&i{EI(uP`>kFEIF4qr2 z+kIi&*ord=Fg&Uy-PjnwCf{`G-+DEJ&r!Dh<+fazXD7`An2tDU!=+R*j)G?=WC)(8 z4SQD9|0130XAjJBc~1KKY2#Ff`q<5RWVNk95O?~AJokr=0^He%}W&u<*cG3=UQgmTGWrzL22zoDJap50V zLC>3+r%+sVfxFV{C5&jsclnjMAF~0yu;f?_2Z5<<7~Dnx&{cTGIKlIAAq5XUFFYbA zT|sR-z2o_QYEi?oQ}Oif6{8%@C?IqzjD3H{XCIwfrlK_ zy+dY;j-!7D=NO9s(AWZxD82v&WMjuf07d32MF92$W?QU!0?8qhPO2R5W=44g`=p6< z&5SQFB~6{HlwcLau}Jrm0uY+>g)eY}F`c1o*6{_#K*Nu2%KelND)L4p%Oril+94FA zjRu=B380=CwbK=mG9Bqi@x}U!sk~webd6+&!79kb$cZ{~-C#2&0U!&mJ)caM5UC=9 ze1QfpM=N+AdO%oc&G{r+OeoVrId1FfpZt}~JM29;-^-QMAtzv^%dTz}_4~>dk{1yS zWWy?I5kNj8owTxVCiM?r*Fwji0i*u7;s!bf-vQ010qNS#tmY3ljhU3ljkVnw%H_00KQpL_t(o!|j$!YZFlv$A9-`MjDDQ zkWlEN<}n~-A%f^iuuBE~0xkrrE=0e8D?fmWqAj}V%AL3pcj99s6hUxf(ImBmtduT9 zN=?$a$3>FK9cku~j)IVZS<4*f$8? zqv2tcedYkuip%!5FFc5{cScQgD;#&=Y)?gUzGnIJ7n;>L0v!T)?>5*NE0b=S{o)~m z*Hof20oX26uzmaz7vQ{W53TmB_`eSjG1= z34n@Kp~4o5ss53Au4|m%$*xmk&dsqN_!&;Og}3>&F%=jl!hMVO+{@{meVlOdZR$pR z@I(McLfgRn3|B{(1^^-~C9Shq0H?usgm-}on(OxbEx?pHq7*ZOu{P1oC*2kt1rhFA z+fdyqGHU~fV!_IOHD9Nh8G!l-y1_F9e=s(c<3?x^yZcG<>{GR3W63IT4WI^eJ&pkf z26L+|9%l~l8t52q2dXIc`Xx?zKu#?A;YGDQR4Oxoc_6<=OW;G`sCz0X#iamDSn3qh zbz7CxP!_1PXulS;GFPk&q0anrVYmDa&{h#G4C4Mm|}{foLDKBAcxp;hIN zEc}9X{1$6tTzIJz_<9&-9V)Kk&sa@WV{V)qHK|U-1NB0x$vi?*M)SbrZT=(g)q)00000NkvXXu0mjf D+rTTB literal 0 HcmV?d00001 diff --git a/src/scripts/runaction/actions/yandeximagesearch.desktop b/src/scripts/runaction/actions/yandeximagesearch.desktop new file mode 100644 index 000000000..60113521b --- /dev/null +++ b/src/scripts/runaction/actions/yandeximagesearch.desktop @@ -0,0 +1,11 @@ +[Desktop Entry] +Name=Yandex +Comment=Search with... + +Icon=yandex.png +Type=Service + +X-RunAction-Type="Url" +X-RunAction-TypeCondition="Image" +X-RunAction-Submenu="search_with" +X-RunAction-Exec="https://yandex.com/images/search?&img_url={url}&rpt=imageview" diff --git a/src/scripts/runaction/button.py b/src/scripts/runaction/button.py new file mode 100644 index 000000000..cd077dc8c --- /dev/null +++ b/src/scripts/runaction/button.py @@ -0,0 +1,50 @@ +# ============================================================ +# RunAction plugin for Falkon +# Copyright (C) 2018 David Rosca +# +# 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 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# ============================================================ +import Falkon +import os +from PySide2 import QtGui, QtWidgets +from runaction.i18n import i18n + +class RunActionButton(Falkon.AbstractButtonInterface): + def __init__(self, manager): + super().__init__() + self.manager = manager + + self.setIcon(QtGui.QIcon(os.path.join(os.path.dirname(__file__), "icon.svg"))) + self.setTitle(i18n("Run Action")) + self.setToolTip(i18n("Run action on current page")) + + self.clicked.connect(self.onClicked) + + def id(self): + return "runaction-button" + + def name(self): + return i18n("RunAction button") + + def onClicked(self, controller): + self.menu = QtWidgets.QMenu() + + for action in self.manager.getActions(self.webView()): + self.menu.addAction(action) + + self.menu.addSeparator() + self.menu.addAction(QtGui.QIcon.fromTheme("configure"), i18n("Configure..."), self.manager.showSettings) + + self.menu.popup(controller.callPopupPosition(self.menu.sizeHint())) + self.menu.aboutToHide.connect(controller.callPopupClosed) diff --git a/src/scripts/runaction/icon.svg b/src/scripts/runaction/icon.svg new file mode 100644 index 000000000..9a1198916 --- /dev/null +++ b/src/scripts/runaction/icon.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/src/scripts/runaction/metadata.desktop b/src/scripts/runaction/metadata.desktop new file mode 100644 index 000000000..0024f9a82 --- /dev/null +++ b/src/scripts/runaction/metadata.desktop @@ -0,0 +1,11 @@ +[Desktop Entry] +Name=Run Action +Comment=Run various actions on sites + +Icon=icon.svg +Type=Service + +X-Falkon-Author=David Rosca +X-Falkon-Email=nowrep@gmail.com +X-Falkon-Version=0.1.0 +X-Falkon-Settings=true diff --git a/src/scripts/runaction/runaction.py b/src/scripts/runaction/runaction.py new file mode 100644 index 000000000..21819534c --- /dev/null +++ b/src/scripts/runaction/runaction.py @@ -0,0 +1,70 @@ +# ============================================================ +# RunAction plugin for Falkon +# Copyright (C) 2018 David Rosca +# +# 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 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# ============================================================ +import Falkon +from PySide2 import QtCore +from runaction import actionmanager, button + +class RunActionPlugin(Falkon.PluginInterface, QtCore.QObject): + buttons = {} + manager = None + + def init(self, state, settingsPath): + plugins = Falkon.MainApplication.instance().plugins() + plugins.mainWindowCreated.connect(self.mainWindowCreated) + plugins.mainWindowDeleted.connect(self.mainWindowDeleted) + + self.manager = actionmanager.ActionManager(settingsPath) + + if state == Falkon.PluginInterface.LateInitState: + for window in Falkon.MainApplication.instance().windows(): + self.mainWindowCreated(window) + + def unload(self): + for window in Falkon.MainApplication.instance().windows(): + self.mainWindowDeleted(window) + + self.manager = None + + def testPlugin(self): + return True + + def populateWebViewMenu(self, menu, view, r): + for action in self.manager.getActions(view, r): + if action.inherits("QMenu"): + menu.addMenu(action).setParent(menu) + else: + action.setParent(menu) + menu.addAction(action) + + def showSettings(self, parent): + self.manager.showSettings(parent) + + def mainWindowCreated(self, window): + b = button.RunActionButton(self.manager) + window.statusBar().addButton(b) + window.navigationBar().addToolButton(b) + self.buttons[window] = b + + def mainWindowDeleted(self, window): + if not window in self.buttons: return + b = self.buttons[window] + window.statusBar().removeButton(b) + window.navigationBar().removeToolButton(b) + del self.buttons[window] + +Falkon.registerPlugin(RunActionPlugin()) diff --git a/src/scripts/runaction/settings.ui b/src/scripts/runaction/settings.ui new file mode 100644 index 000000000..9c2a108dc --- /dev/null +++ b/src/scripts/runaction/settings.ui @@ -0,0 +1,47 @@ + + + RunActionSettings + + + + 0 + 0 + 544 + 492 + + + + Dialog + + + + + + + + + + 16 + 16 + + + + true + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + diff --git a/src/scripts/runaction/settingsdialog.py b/src/scripts/runaction/settingsdialog.py new file mode 100644 index 000000000..5bb128207 --- /dev/null +++ b/src/scripts/runaction/settingsdialog.py @@ -0,0 +1,58 @@ +# ============================================================ +# RunAction plugin for Falkon +# Copyright (C) 2018 David Rosca +# +# 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 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# ============================================================ +import Falkon +import os +from PySide2 import QtCore, QtGui, QtWidgets, QtUiTools +from runaction.i18n import i18n + +class SettingsDialog(QtWidgets.QDialog): + def __init__(self, manager, parent=None): + super().__init__(parent) + + self.manager = manager + + file = QtCore.QFile(os.path.join(os.path.dirname(__file__), "settings.ui")) + file.open(QtCore.QFile.ReadOnly) + self.ui = QtUiTools.QUiLoader().load(file, self) + layout = QtWidgets.QVBoxLayout(self) + layout.addWidget(self.ui) + + self.setMinimumSize(400, 250) + self.setWindowTitle(i18n("Run Action Settings")) + self.ui.label.setText("{}".format(i18n("Available actions"))) + + for action in self.manager.actions: + item = QtWidgets.QListWidgetItem(self.ui.listWidget) + item.setText(action.title) + item.setIcon(action.icon) + item.setData(QtCore.Qt.UserRole, action.id) + item.setFlags(item.flags() | QtCore.Qt.ItemIsUserCheckable) + item.setCheckState(QtCore.Qt.Unchecked if action.id in self.manager.disabledActions else QtCore.Qt.Checked) + self.ui.listWidget.addItem(item) + + self.ui.buttonBox.accepted.connect(self.accept) + self.ui.buttonBox.rejected.connect(self.reject) + + def accept(self): + disabled = [] + for i in range(self.ui.listWidget.count()): + item = self.ui.listWidget.item(i) + if item.checkState() == QtCore.Qt.Unchecked: + disabled.append(item.data(QtCore.Qt.UserRole)) + self.manager.disabledActions = disabled + super().accept()