GtkAppChooser Content type for all applications and/or audio mixers - gtk

I'd like to have a GtkAppChooserButton which allows the user to select a program to run which will most likely want to be an audio mixer such as pavucontrol. Despite vague documentation on the matter I gather the app chooser's content type is meant to be a MIME type, however I cannot find a suitable MIME type for an audio mixer, or more generally just "all applications".
Some types such as application/ will give two Other Application... options if Other... items are enabled, both of which are identical and neither of which contain half the applications I have, including any audio mixers. Aside from that, nothing else I do gets me remotely close to what I'm after.
Is there a MIME type and/or GtkAppChooser content type (they seem to be the same thing?) for audio mixers or just all programs in general? (I.e. Any program that would have an icon in the likes of the Gnome app launcher/xfce4-whisker-menu/etc.)

Alright so I've come up with a solution, thought it may not be as clean as you hoped.
This thread mentioned a way to get the GtkAppChooser to "show all", but it doesn't actually show all applications you have installed. However from that I was able to work out how the GtkAppChooser is using Gio.AppInfo, which has Gio.AppInfo.get_all() (this is for PyObject) which returns a full list of Gio.AppInfos for all applications I have installed, provided they have a .desktop file.
So, my "solution" is to write my own app chooser which gets a list of apps from Gio.AppInfo.get_all().
I've cleaned up my previous solution to this and written a class 'AllAppChooser' to inherrit Gtk.Dialog, giving much greater customisation. The completed dialog looks like so:
And the code used:
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gio
class AllAppChooser(Gtk.Dialog):
"""Provide a dialog to select an app from all those installed.
The regular Gtk.AppChooserDialog does not seem to provide any way to allow
selection from all installed apps, so this dialog serves as a replacement.
"""
def __init__(self, parent=None):
super().__init__(self)
self.set_default_size(350, 400)
self.set_icon_name('gtk-search')
self.set_title('App Chooser')
if parent:
self.set_parent(parent)
self.content_box = self.get_content_area()
self.content_box.set_margin_left(8)
self.content_box.set_margin_right(8)
self.content_box.set_margin_top(8)
self.content_box.set_margin_bottom(8)
self.content_box.set_spacing(8)
self.button_box = self.get_action_area()
self.button_box.set_margin_left(4)
self.button_box.set_margin_right(4)
self.button_box.set_margin_top(4)
self.button_box.set_margin_bottom(4)
self.label = Gtk.Label('Choose An Application')
self.content_box.pack_start(self.label, False, False, 0)
self.list_store = Gtk.ListStore(str, str, int)
pixbuf_renderer = Gtk.CellRendererPixbuf()
text_renderer = Gtk.CellRendererText()
icon_column = Gtk.TreeViewColumn('icon', pixbuf_renderer, icon_name=1)
text_column = Gtk.TreeViewColumn('text', text_renderer, text=0)
self.tree_view = Gtk.TreeView()
self.tree_view.set_model(self.list_store)
self.tree_view.set_headers_visible(False)
self.tree_view.append_column(icon_column)
self.tree_view.append_column(text_column)
self.view_port = Gtk.Viewport()
self.view_port.add(self.tree_view)
self.scroll_window = Gtk.ScrolledWindow()
self.scroll_window.add(self.view_port)
self.content_box.pack_start(self.scroll_window, True, True, 0)
self.ok_button = self.add_button(Gtk.STOCK_OK, 1)
self.ok_button.connect('clicked', self.on_ok)
self.cancel_button = self.add_button(Gtk.STOCK_CANCEL, 0)
self.selected_app = None
self.app_list = []
def populate_app_list(self):
"""Populate the list of apps with all installed apps.
Icons are provided by icon-name, however some apps may return a full
path to a custom icon rather than a themed-icon name, or even no name
at all. In these cases the generic 'gtk-missing-icon' icon is used.
"""
self.app_list = Gio.AppInfo.get_all()
for i in range(len(self.app_list)):
gio_icon = self.app_list[i].get_icon()
app_icon = 'gtk-missing-icon'
if gio_icon:
app_icon = gio_icon.to_string()
app_name = self.app_list[i].get_display_name()
self.list_store.append([app_name, app_icon, i])
self.list_store.set_sort_column_id(0, Gtk.SortType.ASCENDING)
def run(self):
"""Run the dialog to get a selected app."""
self.populate_app_list()
self.show_all()
super().run()
self.destroy()
return self.selected_app
def set_label(self, text):
"""Set the label text, \"Choose An App\" by default."""
self.label.set_text(text)
def on_ok(self, button):
"""Get Gio.AppInfo of selected app when user presses OK."""
selection = self.tree_view.get_selection()
tree_model, tree_iter = selection.get_selected()
app_index = tree_model.get_value(tree_iter, 2)
self.selected_app = self.app_list[app_index]
This is then run similar to a regular dialog:
app_chooser = AllAppChooser()
application = app_chooser.run()
If the user exits the dialog box or presses cancel then the result will be None, but if they selected an application then run() will return a Gio.AppInfo object for the application which you can then do with as you please. For example, to launch your newly selected application:
application.launch()
I feel this is now a relatively solid solution, but I still welcome additional suggestions. Additionally, if there is a way to do this in Gtk without having to do all this stuff I would still love to hear it.

Related

Adding checkable combobox in QGIS plugin builder plugin

I am developing a QGIS plugin in python and hit a roadblock when displaying my GUI. I am using the plugin builder framework to develop my plugin and I have trouble displaying a checkable combo box in a scrollArea in my GUI. The code with core functionality is as follows.
def run(self):
# Only create GUI ONCE in callback, so that it will only load when the plugin is started
if self.first_start == True:
self.first_start = False
# Sets up the pyqt user interface
self.dlg = EarthingToolDialog()
# Fetching the active layer in the QGIS project
layer = self.iface.activeLayer()
checkable_combo = CheckableComboBox()
# Going through each field of the layer
# and adding field names as items to the
# combo box
for j,field in enumerate(layer.fields()):
checkable_combo.addItem(str(field.name()))
# Setting the checked state to True by default
checkable_combo.setItemChecked(j, True)
# putting the check box inside the scroll area of the GUI
self.dlg.scrollArea.setWidget(checkable_combo)
self.dlg.scrollArea.setMinimumSize(QSize(700,400))
# show the dialog
self.dlg.show()
# Run the dialog event loop
self.dlg.exec_()
class EarthingToolDialog(QtWidgets.QDialog, FORM_CLASS):
def __init__(self, parent=None):
"""Constructor."""
super(EarthingToolDialog, self).__init__(parent)
self.setupUi(self)
class CheckableComboBox(QComboBox):
def __init__(self):
super().__init__()
self._changed = False
self.view().pressed.connect(self.handleItemPressed)
def setItemChecked(self, index, checked=False):
print('checked')
item = self.model().item(index, self.modelColumn()) # QStandardItem object
print(type(item))
if checked:
item.setCheckState(Qt.CheckState.Checked)
else:
item.setCheckState(Qt.CheckState.Unchecked)
def handleItemPressed(self, index):
print('pressed')
item = self.model().itemFromIndex(index)
if item.checkState() == Qt.Checked:
item.setCheckState(Qt.Unchecked)
else:
item.setCheckState(Qt.Checked)
self._changed = True
print('set ' + str(item.checkState()))
def hidePopup(self):
print('hide')
if not self._changed:
super().hidePopup()
self._changed = False
def itemChecked(self, index):
print('read')
item = self.model().item(index, self.modelColumn())
return item.checkState() == Qt.Checked
In summary, the run function is the main function called by the plugin when it is loaded. self.dlg is the instance of the actual pyqt python user interface. This is rendered with the help of the EarthingToolDialog class. The checkable combo box and it's functionalities are self contained in the CheckableComboBox class.
The run function executes without any error when the plugin is loaded but the checkboxes are not visible in the combobox. Just a normal combo box with a list of items (just the standard dropdown combo box) is seen on the GUI's scroll area and not the desired checkable combo box. The CheckableComboBox class was taken from https://morioh.com/p/d1e70112347c and it runs perfectly well in the demo code shown there.
I understand that this is a very specific question and it would be great if someone could figure out what the problem might be. Thanks in advance!
Within the run function, this piece of codes didn't work for me:
self.dlg.scrollArea.setWidget(checkable_combo)
self.dlg.scrollArea.setMinimumSize(QSize(700,400))
So instead, I use:
layout = QVBoxLayout()
layout.addWidget(checkable_combo)
self.dlg.setLayout(layout)
I didn't use directly this class (It was generated automatically since I use Plugin Builder, so in here I commented it):
class EarthingToolDialog(QtWidgets.QDialog, FORM_CLASS):
def __init__(self, parent=None):
"""Constructor."""
super(EarthingToolDialog, self).__init__(parent)
self.setupUi(self)
Now, in order to display checkable combo box, CheckableComboBox constructor is changed as :
def __init__(self):
super().__init__()
self._changed = False
self.view().pressed.connect(self.handleItemPressed)
delegate = QtWidgets.QStyledItemDelegate(self.view())
self.view().setItemDelegate(delegate)
The last two lines are from the answer listed here.
Codes display checkable combo box list with all items checked by default.

is there a way to take numerical inputs in gtk+, pygobject?

I need to implement a input dialog box that takes in numerical values. How is it done in Pygobject?. It is similar to excel taking numerical inputs.
If you're looking for simple numerical values, you can use Gtk.SpinButton.
The more general way getting input from the user in the UI is Gtk.Entry. That widget does not support built-in validation, but it can be trivially implemented, similarly as shown in this stackoverflow answer (by overriding the insert_text method).
If you want to show validation errors to your user, you can use either CSS styling, or for example set the secondary icon (with e.g. the secondary_icon_name property) to a warning sign, with an accompanying label explaining what is wrong.
With some help from Ricardo Silva Veloso, this is basically the code I was suggesting to write in my first comment (also considering #nielsdg secondary icon suggestion):
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk
class MyEntry(Gtk.Entry, Gtk.Editable):
__gtype_name__ = 'MyEntry'
def __init__(self, **kwargs):
super(MyEntry, self).__init__(**kwargs)
def do_insert_text(self, text, length, position):
if text.isnumeric():
self.props.secondary_icon_name = None
self.get_buffer().insert_text(position, text, length)
return length + position
else:
self.props.secondary_icon_name = "dialog-error"
self.props.secondary_icon_tooltip_text = "Only numbers are allowed"
return position
win = Gtk.Window()
myentry = MyEntry()
win.add(myentry)
win.connect("destroy", Gtk.main_quit)
win.show_all()
Gtk.main()

How to have an Apps Script Gadget display results directly in the UI

On my Google Site I've inserted an Apps Script Gadget (by pasting the URL of an Apps Script that I published as a service). This Apps Script allows the user to enter a value (their 'Blow Number') and view the corresponding data (based on API calls to my Google Fusion tables).
Right now, the script returns 3 hyperlinks:
Click here for a table of Blow Number 1
Click here for a chart of Blow Number 1
Click here for a map of Blow Number 1
This is because my script function getblowdetails has 3 app.createAnchor variables. Instead of having the script return 3 hyperlinks (that the user has to click on and view the resulting URL in a new window), I would like for the script to automatically invoke the 3 URLs and display the table, chart, and map in panels on the same page.
So the user would enter their Blow Number and press enter. They would then view the table, chart, and map directly below the text box on the same web page.
Please see the Code that I've included below and advise...Thanks for the help- I'm an apps-script novice so a thorough and understand-able response is greatly appreciated!
Code
Note: I've removed the URLs from the createAnchor variables because I'm only allowed to include 2 links in the post, but you can see them by going to www.OnSiteBAC.com/ViewMyBlows and entering Blow Number = 1...then click on the hyperlinks.
function doGet() {
var app = UiApp.createApplication();
// Create input boxes, buttons, labels, and links
var textBoxA = app.createTextBox().setId('textBoxA').setName('textBoxA').setFocus(true);
var buttonA = app.createButton('Get Blow Details').setEnabled(false);
var label = app.createLabel('Please enter your Blow Number here');
var link = app.createAnchor('where can I find my Blow Number?', 'http://www.onsitebac.com');
// Create a handler to call the getblowdetails function.
// A validation is added to this handler so that it will only invoke 'getblowdetails' if textBoxA contains a number
var handler = app.createServerClickHandler('getblowdetails').validateNumber(textBoxA).addCallbackElement(textBoxA);
// Create a handler to enable the button if all input is legal
var onValidInput = app.createClientHandler().validateNumber(textBoxA).forTargets(buttonA).setEnabled(true).forTargets(label, link).setVisible(false);
// Create a handler to mark invalid input in textBoxA and disable the button
var onInvalidInput1 = app.createClientHandler().validateNotNumber(textBoxA).forTargets(buttonA).setEnabled(false).forTargets(textBoxA).setStyleAttribute("color", "red").forTargets(label, link).setVisible(true);
// Create a handler to mark the input in textBoxA as valid
var onValidInput1 = app.createClientHandler().validateNumber(textBoxA).forTargets(textBoxA).setStyleAttribute("color", "black");
// only fire ServerHandler for onKeyUp if it passes validation
var textBoxHandler = app.createServerHandler('textBoxHandlerFunction');
// Add all the handlers to be called when the user types in the text boxes
textBoxHandler.addCallbackElement(textBoxA);
textBoxA.addKeyUpHandler(onInvalidInput1);
textBoxA.addKeyUpHandler(onValidInput1);
textBoxA.addKeyUpHandler(onValidInput);
textBoxA.addKeyUpHandler(textBoxHandler);
buttonA.addClickHandler(handler);
app.add(textBoxA);
app.add(buttonA);
app.add(label);
app.add(link);
return app;
}
function textBoxHandlerFunction(e) {
var app = UiApp.getActiveApplication();
if (e.parameter.keyCode == 13)
{
app = getblowdetails(e);
}
return app;
}
function getblowdetails(e) {
var app = UiApp.getActiveApplication();
var panel2 = app.createVerticalPanel();
var link2 = app.createAnchor ().setStyleAttribute("color", "green");
var panel3 = app.createVerticalPanel();
var link3 = app.createAnchor ();
var panel4 = app.createVerticalPanel();
var link4 = app.createAnchor ();
panel3.add(link3);
app.add(panel3);
panel4.add(link4);
app.add(panel4);
return app;
}
I don't think you'll be able to actually download the result and show it. So, there's no easy solution.
But you can build that table and chart on Apps Script easily (assuming you can already fetch the info from tables using its API).
The last issue is the map. On Apps Script you can only create static maps, meaning, an image. You can add custom markers and polygons, set the zoom, etc. But in the end it's a photo. The user will not be able to drag it around or use the map as an embedded google map as one would expect.

Google's autocomplete not activated by pasting with mouse

The Google autocomplete API doesn't seem to be activating by pasting content into a text input with the mouse. It works fine if involving the keyboard at all, but not with just mouse.
I did notice, however, that after you paste your content into the text input it will activate from almost any keypress (tested right arrow key, end key, space).
You can repro it here on their autocomplete demo site.
Is this a bug? or as designed? If it's as designed, how to apply workaround?
I've got this as a workaround so far, but no simulated keypress events seem to work.
$('.txtLocation').bind("paste", function (e)
{
$('.txtLocation').focus();
var e = jQuery.Event("keydown");
e.keyCode = 39; //39=Arrow Right
$('.txtLocation').trigger(e);
});
It seems this impacts not only the context-menu Paste, but also that of Edit|Paste from the browser menu bar as well as the iOS paste functionality. I've opened a bug with Google. You may wish to "Star" that bug report to catch updates.
I found a workaround that, while a bit of a hack, seems to fix the problem. If you store the pasted value, switch focus on a different field, set the value in the Autocomplete field, and finally focus back on the Autocomplete field things work more or less as expected. Also, you have to do this in a setTimeout() callback - the delay time doesn't seem to matter at all, but if you just do this inline you won't see the expected results.
Here's a code sample of what I'm describing above:
$("#address_field").on("paste", googleMapsAutocompletePasteBugFix);
googleMapsAutocompletePasteBugFix = function() {
return setTimeout(function() {
var field, val;
field = $("#address_field");
val = field.val();
$("#price").focus();
field.val(val);
return field.focus();
}, 1);
};
The last focus() is optional, but the UI is a little less surprising than if you just skipped automatically to the next field.
Following solution seems to work for me (existence of field ending with "address_2" is assumed). Tested on IE8, IE9, IE10, Chrome, FF and Safari
if document.addEventListener
$(document).on("paste", "[name*=address_1]", #googleMapsAutocompletePasteBugFix)
$(document).on("onpaste", "[name*=address_1]", #googleMapsAutocompletePasteBugFix)
else
for element in $("input[name*=address_1]")
document.getElementById($(element).attr('id')).onpaste = #googleMapsAutocompletePasteBugFix
googleMapsAutocompletePasteBugFix: (e) ->
unless e
e = window.event
if e.srcElement
target = e.srcElement
else
target = e.target
field = $(target)
fieldId = field.attr('id')
focusSwitchFieldId = fieldId.replace(/(\d)$/, '2')
setTimeout(->
if window.chrome || /Safari/.test(navigator.userAgent)
val = field.val()
$("##{focusSwitchFieldId}").focus()
field.val(val)
field.focus()
else
field = document.getElementById(fieldId)
val = field.value
document.getElementById(focusSwitchFieldId).focus()
setTimeout(->
field.value = val
field.focus()
field.focus()
, 50)
, 10)

Receiving keystrokes in an opened wx.ComboCtrl

Coming from this question, I have a wxComboCtrl with a custom popup made of a panel with a bunch of radiobuttons.. My problem is that when I open the popup the combo doesn't get keystrokes, because the events get handled by the panel itself.. I'd like to redirect those KeyEvents to the textctrl of the combo, but I can't find a way to get it to work :/
Am I going the wrong way? Should I manually handle the textctrl value as the user presses keys? I think that would be a bit cumbersome though.. Since supposedly the textctrl already knows how to handle those events..
Here's my testcase (wxPython 2.8 on Linux), the "on_key" method should be the culprit:
import wx
import wx.combo
class CustomPopup(wx.combo.ComboPopup):
def Create(self, parent):
# Create the popup with a bunch of radiobuttons
self.panel = wx.Panel(parent)
sizer = wx.GridSizer(cols=2)
for x in range(10):
r = wx.RadioButton(self.panel, label="Element "+str(x))
r.Bind(wx.EVT_RADIOBUTTON, self.on_selection)
sizer.Add(r)
self.panel.SetSizer(sizer)
# Handle keyevents
self.panel.Bind(wx.EVT_KEY_UP, self.on_key)
def GetControl(self):
return self.panel
def GetAdjustedSize(self, minWidth, prefHeight, maxHeight):
return wx.Size(200, 150)
def on_key(self, evt):
if evt.GetEventObject() is self.panel:
# Trying to redirect the key event to the combo.. But this always returns false :(
print self.GetCombo().GetTextCtrl().GetEventHandler().ProcessEvent(evt)
evt.Skip()
def on_selection(self, evt):
self.Dismiss()
wx.MessageBox("Selection made")
class CustomFrame(wx.Frame):
def __init__(self):
# Toolbar-shaped frame with a ComboCtrl
wx.Frame.__init__(self, None, -1, "Test", size=(800,50))
combo = wx.combo.ComboCtrl(self)
popup = CustomPopup()
combo.SetPopupControl(popup)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(combo, 0)
self.SetSizer(sizer)
self.Layout()
if __name__ == '__main__':
app = wx.PySimpleApp()
CustomFrame().Show()
app.MainLoop()
Edit:
I found these (unresolved) discussions on the same topic..
"ComboCtrl loses keyboard focus when ComboPopup is shown "
"issue using wx.ComboCtrl"
I had the same problem with a custom ComboPopup control using a panel as the basis of the control.
EmulateKeypress just entered a never-ending loop. I found binding the panel to the EVT_KEY_DOWN and EVT_CHAR to the overridden OnComboKeyEvent function and skipping the event down the event handling chain was all that was required to enable entering text into the textbox of the ComboCtrl with the popup open. I think this makes sense. As the Popup isn't strictly a control its events won't belong anywhere until bound into the the wx.app event loop through the Bind() method.
I've included the overridden OnComboKeyEvent function below so that anyone else can explore this problem.
def create(self, *args, **kwds):
...
self.panel.Bind(wx.EVT_CHAR, self.OnComboKeyEvent)
self.panel.Bind(wx.EVT_KEY_DOWN, self.OnComboKeyEvent)
...
def OnComboKeyEvent(self, evt):
evt_dict = {wx.EVT_TEXT.typeId: "TextEvent",
wx.EVT_CHAR.typeId:"CharEvent",
wx.EVT_CHAR_HOOK.typeId:"CharHookEvent",
wx.EVT_KEY_UP.typeId:"KeyUpEvent",
wx.EVT_KEY_DOWN.typeId:"KeyDownEvent"}
if evt.GetEventType() in evt_dict:
thelogger.debug("oncombokeyevet:%s" % evt_dict[evt.GetEventType()])
super().OnComboKeyEvent(evt)
evt.Skip()
I've got an answer from Robin Dunn himself on wxpython-users:
Sending low-level events to native
widgets like this does not work the
way that most people expect it to.
You are expecting this to result in
the character being added to the text
ctrl,but it can't do that because all
that ProcessEvent does is send the
wx.Event to any bound event handlers
and then it stops. It does not go to
the next step and convert that event
to the equivalent native message and
send it on to the native widget. See
the "Simulating keyboard events"
thread.
It doesn't work especially well on
non-Windows platforms, but you could
try calling EmulateKeypress instead.