Implement a userEdited signal to QDateTimeEdit? - python

QLineEdit has a textEdited signal which is emitted whenever the text is changed by user interaction, but not when the text is changed programatically. However, QDateTimeEdit has only a general dateTimeChanged signal that does not distinguish between these two types of changes. Since my app depends on knowing if the field was edited by the user or not, I'm looking for ways to implement it.
My (currently working) strategy was to create an eventFilter to the edit field, intercept key press and mouse wheel events and use them to determine if the field was modified by the user (storing this info in an object), and finally connecting the dateTimeChanged signal to a function that decides if the change was by the user or done programatically. Here are the relevant parts of the code (python):
class UserFilter(QObject):
def __init__(self, parent):
QObject.__init__(self, parent)
self.parent = parent
def eventFilter(self, object, event):
if event.type() == QEvent.KeyPress or event.type() == QEvent.Wheel:
self.parent.edited = True
else:
pass
return False
class DockThumb(QWidget):
def __init__(self, parent):
QWidget.__init__(self, parent)
self.parent = parent
self.edited = False
self.dateedit = QDateTimeEdit(self)
self.userfilter = UserFilter(self)
self.dateedit.installEventFilter(self.userfilter)
...
self.connect(self.dateedit,
SIGNAL('dateTimeChanged(QDateTime)'),
self.edited_or_not)
def edited_or_not(self):
if self.edited:
# User interacted! Go for it.
self.parent.runtimer()
# self.edited returns to False once data is saved.
else:
# User did not edited. Wait.
pass
Is there a more objective way of doing it? I tried subclasssing QDateTimeEdit, but failed to deal with events... Expected user interactions are direct typing, up/down arrow keys to spin through dates and copy/pasting the whole string.

The idiomatic Qt way of achieving this is indeed subclassing QDateTimeEdit and adding the functionality you require. I understand you tried it and "failed to deal with events", but that's a separate issue, and perhaps you should describe those problems - since they should be solvable.

Since I'm not entirely sure about what you are trying to do, I would agree with Eli Bendersky. Short of that, if you know when you will be programatically changing the QDateTimeEdit, set some flag that you can check in the slot handler that will indicate a programatic change is occurring and clear it when you are done.

Related

How do I catch the PyQt5 QSplitter.resizeEvent?

I can catch the user resizing or repositioning the window simply by defining functions:
def resizeEvent(self, event): ...
def moveEvent(self, event): ...
But I also have 2 QSplitter, and would like to know any new split the user has applied to make it also a default for the next start. QSplitter also has a resizeEvent(), but I can't catch it, because it is already used by the apparently higher ranking functions above.
How can I get hold of QSplitter's resizeEvent?
I now use as a workaround def paintEvent(self, event):... which does allow me to get the needed info, but it feels a bit clumsy :-/

Qt - update view size on delegate sizeHint change

I have a QTreeView with a QStyledItemDelegate inside of it. When a certain action occurs to the delegate, its size is supposed to change. However I haven't figured out how to get the QTreeView's rows to resize in response to the delegate's editor size changing. I tried QTreeView.updateGeometry and QTreeView.repaint and a couple other things but it doesn't seem to work. Could someone point me in the right direction?
Here's a minimal reproduction (note: The code is hacky in a few places, it's just meant to be a demonstration of the problem, not a demonstration of good MVC).
Steps:
Run the code below
Press either "Add a label" button
Note that the height of the row in the QTreeView does not change no matter how many times either button is clicked.
from PySide2 import QtCore, QtWidgets
_VALUE = 100
class _Clicker(QtWidgets.QWidget):
clicked = QtCore.Signal()
def __init__(self, parent=None):
super(_Clicker, self).__init__(parent=parent)
self.setLayout(QtWidgets.QVBoxLayout())
self._button = QtWidgets.QPushButton("Add a label")
self.layout().addWidget(self._button)
self._button.clicked.connect(self._add_label)
self._button.clicked.connect(self.clicked.emit)
def _add_label(self):
global _VALUE
_VALUE += 10
self.layout().addWidget(QtWidgets.QLabel("Add a label"))
self.updateGeometry() # Note: I didn't expect this to work but added it regardless
class _Delegate(QtWidgets.QStyledItemDelegate):
def createEditor(self, parent, option, index):
widget = _Clicker(parent=parent)
viewer = self.parent()
widget.clicked.connect(viewer.updateGeometries) # Note: I expected this to work
return widget
def paint(self, painter, option, index):
super(_Delegate, self).paint(painter, option, index)
viewer = self.parent()
if not viewer.isPersistentEditorOpen(index):
viewer.openPersistentEditor(index)
def setEditorData(self, editor, index):
pass
def updateEditorGeometry(self, editor, option, index):
editor.setGeometry(option.rect)
def sizeHint(self, option, index):
hint = index.data(QtCore.Qt.SizeHintRole)
if hint:
return hint
return super(_Delegate, self).sizeHint(option, index)
class _Model(QtCore.QAbstractItemModel):
def __init__(self, parent=None):
super(_Model, self).__init__(parent=parent)
self._labels = ["foo", "bar"]
def columnCount(self, parent=QtCore.QModelIndex()):
return 1
def data(self, index, role):
if role == QtCore.Qt.SizeHintRole:
return QtCore.QSize(200, _VALUE)
if role != QtCore.Qt.DisplayRole:
return None
return self._labels[index.row()]
def index(self, row, column, parent=QtCore.QModelIndex()):
child = self._labels[row]
return self.createIndex(row, column, child)
def parent(self, index):
return QtCore.QModelIndex()
def rowCount(self, parent=QtCore.QModelIndex()):
if parent.isValid():
return 0
return len(self._labels)
application = QtWidgets.QApplication([])
view = QtWidgets.QTreeView()
view.setModel(_Model())
view.setItemDelegate(_Delegate(parent=view))
view.show()
application.exec_()
How do I get a single row in a QTreeView, which has a persistent editor applied already to it, to tell Qt to resize in response to some change in the editor?
Note: One possible solution would be to close the persistent editor and re-open it to force Qt to redraw the editor widget. This would be generally very slow and not work in my specific situation. Keeping the same persistent editor is important.
As the documentation about updateGeometries() explains, it:
Updates the geometry of the child widgets of the view.
This is used to update the widgets (editors, scroll bars, headers, etc) based on the current view state. It doesn't consider the editor size hints, so that call or the attempt to update the size hint is useless (and, it should go without saying, using global for this is wrong).
In order to properly notify the view that a specific index has updated its size hint, you must use the delegate's sizeHintChanged signal, which should also be emitted when the editor is created in order to ensure that the view makes enough room for it; note that this is normally not required for standard editors (as, being they temporary, they should not try to change the layout of the view), but for persistent editors that are potentially big, it may be necessary.
Other notes:
calling updateGeometry() on the widget is pointless in this case, as adding a widget to a layout automatically results in a LayoutRequest event (which is what updateGeometry() does, among other things);
as explained in createEditor(), "the view's background will shine through unless the editor paints its own background (e.g., with setAutoFillBackground())";
the SizeHintRole of the model should always return a size important for the model (if any), not based on the editor; it's the delegate responsibility to do that, and the model should never be influenced by any of its views;
opening a persistent editor in a paint event is wrong; only drawing related aspects should ever happen in a paint function, most importantly because they are called very often (even hundreds of times per second for item views) so they should be as fast as possible, but also because doing anything that might affect a change in geometry will cause (at least) a recursive call;
signals can be "chained" without using emit: self._button.clicked.connect(self.clicked) would have sufficed;
Considering all the above, there are two possibilities. The problem is that there is no direct correlation between the editor widget and the index it's referred to, so we need to find a way to emit sizeHintChanged with its correct index when the editor is updated.
This can only be done by creating a reference of the index for the editor, but it's important that we use a QPersistentModelIndex for that, as the indexes might change while a persistent editor is opened (for example, when sorting or filtering), and the index provided in the arguments of delegate functions is not able to track these changes.
Emit a custom signal
In this case, we only use a custom signal that is emitted whenever we know that the layout is changed, and we create a local function in createEditor that will eventually emit the sizeHintChanged signal by "reconstructing" the valid index:
class _Clicker(QtWidgets.QWidget):
sizeHintChanged = QtCore.Signal()
def __init__(self, parent=None):
super().__init__(parent)
self.setAutoFillBackground(True)
layout = QtWidgets.QVBoxLayout(self)
self._button = QtWidgets.QPushButton("Add a label")
layout.addWidget(self._button)
self._button.clicked.connect(self._add_label)
def _add_label(self):
self.layout().addWidget(QtWidgets.QLabel("Add a label"))
self.sizeHintChanged.emit()
class _Delegate(QtWidgets.QStyledItemDelegate):
def createEditor(self, parent, option, index):
widget = _Clicker(parent)
persistent = QtCore.QPersistentModelIndex(index)
def emitSizeHintChanged():
index = persistent.model().index(
persistent.row(), persistent.column(),
persistent.parent())
self.sizeHintChanged.emit(index)
widget.sizeHintChanged.connect(emitSizeHintChanged)
self.sizeHintChanged.emit(index)
return widget
# no other functions implemented here
Use the delegate's event filter
We can create a reference for the persistent index in the editor, and then emit the sizeHintChanged signal in the event filter of the delegate whenever a LayoutRequest event is received from the editor:
class _Clicker(QtWidgets.QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setAutoFillBackground(True)
layout = QtWidgets.QVBoxLayout(self)
self._button = QtWidgets.QPushButton("Add a label")
layout.addWidget(self._button)
self._button.clicked.connect(self._add_label)
def _add_label(self):
self.layout().addWidget(QtWidgets.QLabel("Add a label"))
class _Delegate(QtWidgets.QStyledItemDelegate):
def createEditor(self, parent, option, index):
widget = _Clicker(parent)
widget.index = QtCore.QPersistentModelIndex(index)
return widget
def eventFilter(self, editor, event):
if event.type() == event.LayoutRequest:
persistent = editor.index
index = persistent.model().index(
persistent.row(), persistent.column(),
persistent.parent())
self.sizeHintChanged.emit(index)
return super().eventFilter(editor, event)
Finally, you should obviously remove the SizeHintRole return in data(), and in order to open all persistent editors you could do something like this:
def openEditors(view, parent=None):
model = view.model()
if parent is None:
parent = QtCore.QModelIndex()
for row in range(model.rowCount(parent)):
for column in range(model.columnCount(parent)):
index = model.index(row, column, parent)
view.openPersistentEditor(index)
if model.rowCount(index):
openEditors(view, index)
# ...
openEditors(view)
I had a similar problem when I was adding new widgets to a QFrame, because the dumb thing was not updating the value of its sizeHint( ) after adding each new widget. It seems that QWidgets (including QFrames) only update their sizeHint( ) when the children widgets are "visible". Somehow, in some occassions Qt sets new children to "not visible" when they are added, don't ask me why. You can see if a widget is visible by calling isVisible( ), and change its visibility status with setVisible(...). I solved my problem by telling to the QFrame that they new child widgets were intended to be visible, by calling setVisible( True ) with each of each child after adding them to the QFrame. Some Qt fundamentalists may say that this is a blasphemous hack that breaks the fabric of space time or something and that I should be burnt at the stake, but I don't care, it works, and it works very well in a quite complex GUI that I have built.

Proper way to deal with options in a pop up window

I have some items in a list which decrease a internal value, when this value is 0, a windows pops and ask for what to do, there are 3 options, set the item as 'completed', set the item as 'missed', set the item as 'delayed'.
The window is a QDockWidget and options are selected via QPushButtons, I want to connect them to a function that will deal with each of the 3 actions possible.
like
self.options_button_completed.clicked.connect(self.set_completed)
self.options_button_missed.clicked.connect(self.set_missed)
self.options_button_delayed.clicked.connect(self.set_delayed)
But I can't do this way because I need the reference to the item that raised the window in the first place
I wonder if it's possible to set the clicked slot in a way that it will also pass a extra argument, the item who raised the QDockWidget.
Is it possible? Or else, what's the proper way to deal with this?
I assume that I would need to keep a variable with the item, but I'm looking for a more clean way, without clogging the class with variables.
By making the Window a separated QWidget I'm able to instantiate it in the main window and pass and extra argument(item) which will be a instance attribute.
class MainFrame(QWidget):
def __init__(self):
self.popup_windows = [] # to store the pops
def display_popup_window(self, item):
# item is the reference item that it's internal value reached 0
popup_window = PopupFrame(self, item)
popup_window.show()
popup_window._raise()
self.popup_windows.append(popup_window)
class PopupFrame(QWidget):
def __init__(self, parent, item):
self.parent = parent
self.item = item
# set up other things, like buttons, layout...
self.options_button_completed.clicked.connect(self.set_completed)
self.options_button_missed.clicked.connect(self.set_missed)
self.options_button_delayed.clicked.connect(self.set_delayed)
def set_completed(self):
# do something with self.item
pass
It's simplified just to convey the general idea, if anyone need a working example, feel free to ask in the comments and I'll provided.

Single Responsibility Principle for CRUD operations against a collection

I'm trying to understand how to logically separate CRUD responsibilities so to adhere to the Single Responsibility Principle (SRP).
As I understand the definition of SRP, a single responsibility may not necessarily be a single behavior, but instead be a collection of behaviors with a well-defined, logical boundary from others.
In my example, RestaurantMenu is nothing more than a collection. I understand that there are more efficient ways to represent this, such as with a dictionary, but that is beyond the intent of this example. My RestaurantMenu has no behavior assigned to it because it remains unclear to me as to whether defining any further behavior by it would breach the SRP. It feels rather uncomfortable instantiating and calling separate CRUD objects through a Manager object rather than through methods in RestaurantMenu, so that is why I've decided to ask the audience here for some guidance.
Does the following example pass the SRP litmus test?
class RestaurantMenu(object):
def __init__(self, title, creator, catalog_type, restaurant):
self._title = title
self._creator = creator
self._catalog_type = catalog_type
self._restaurant = restaurant
self._menuitems = dict()
class MenuManager(object):
"""Responsibility
--------------
Coordinates CRUD related activities with a menu
"""
def __init__(self, menu):
self._menu = menu
def add_menu_item(self, item, value):
menu_item_adder = AddMenuItem(self._menu)
menu_item_adder(item, value)
def del_menu_item(self, item):
menu_item_deleter = DelMenuItem(self._menu)
menu_item_deleter(item)
def update_menu_item(self, existing_item, new_info):
menu_item_updater = UpdateMenuItem(self._menu)
menu_item_updater(existing_item, new_info)
def get_menu_items(self):
menu_item_getter = GetMenuItems(self._menu)
menu_item_getter()
class GetMenuItems(object):
def __init__(self, menu):
self._menu = menu
def __call__(self):
print(self._menu._title)
print('='*len(self._menu._title))
for key, value in self._menu._menuitems.items():
print(key, value)
class AddMenuItem(object):
def __init__(self, menu):
self._menu = menu
def __call__(self, item, value):
if item not in self._menu._menuitems:
self._menu._menuitems[item] = value
print('Item added:', item)
else:
print('Item already exists. Please update instead.')
class DelMenuItem(object):
def __init__(self, menu):
self._menu = menu
def __call__(self, item):
popped = self._menu._menuitems.pop(item)
print('Item removed:', popped)
class UpdateMenuItem(object):
def __init__(self, menu):
self._menu = menu
def __call__(self, existing_item, new_info):
self._menu._menuitems.update(existing_item=new_info)
print('Item updated:', existing_item, ' with', new_info)
def main():
mymenu = RestaurantMenu("Joe's Crab Shack 2014 Menu",
"Joe Schmoe",
"Restaurant",
"Joe's Crab Shack")
menumanager = MenuManager(mymenu)
menumanager.add_menu_item('longneck_clams', 7.00)
menumanager.add_menu_item('1 pound lobster', 15.00)
menumanager.add_menu_item('lobster chowder', 9.00)
print('-'*50)
menumanager.get_menu_items()
if __name__ == "__main__":
main()
One possible definition of SRP compliance is that there should only be one reason for a class to change.
This makes it very hard to call SRP or not on a piece of code in the abstract -- it basically depends on what will happen to evolve together and separately over time in your application.
Generally speaking though, the UI is one of the primary things that might evolve independently from other parts of a program. Users will keep wanting to make little display adjustments over the course of a project, and it's a nice thing to be able to modify the presentation logic without fearing to break the rest of the system. Persistence is another thing you might want to change, either as a result of new architectural decisions or temporarily, depending on the context (swapping in dummy persistence objects in tests for instance).
This is why in most real-world applications, I would tend to split up classes by technical responsibility rather than business operations on a same entity like C/R/U/D.
If you look closely at your current implementation, you'll notice patterns in your classes. They all fiddle with a MenuManager and the MenuItems stored in it. They all print things to the screen.
If you want to change something in the way data is displayed or stored, you'll basically have to touch all these classes. I'm not saying it's a serious flaw in the case of a small simple system like this, but in a larger application it might well be a problem.
Put another way, your example makes it easy to have menu updates done through a graphical interface into a SQL database, menu inserts done via a command shell into flat files, and menu reads spitting out an XML file with data gathered from a web service. This might be what you want to do in very particular circumstances, but not most of the time...
I just want to complement #guillaume31 answer, but I don't think it will fit in a comment.
As I understand the definition of SRP, a single responsibility may not necessarily be a single behavior, but instead be a collection of behaviors with a well-defined, logical boundary from others.
However you say you understand this, your code shows the opposite. You've spread a high cohesive group of tasks through several classes. Why this is bad?
How many times do you have the following code?
def __init__(self, menu):
self._menu = menu
I'm to lazy to count it, but you'll notice that this is an unecessary code duplication.
In this particular simple case, there is no problem, in deed, but if you application grow, you'll have a huge headache.
In some countries, it's valentine's day tomorrow, so you should remember how to KISS.

How would you design a very "Pythonic" UI framework?

I have been playing with the Ruby library "shoes". Basically you can write a GUI application in the following way:
Shoes.app do
t = para "Not clicked!"
button "The Label" do
alert "You clicked the button!" # when clicked, make an alert
t.replace "Clicked!" # ..and replace the label's text
end
end
This made me think - how would I design a similarly nice-to-use GUI framework in Python? One that doesn't have the usual tyings of basically being wrappers to a C* library (In the case of GTK, Tk, wx, QT etc etc)
Shoes takes things from web devlopment (like #f0c2f0 style colour notation, CSS layout techniques, like :margin => 10), and from ruby (extensively using blocks in sensible ways)
Python's lack of "rubyish blocks" makes a (metaphorically)-direct port impossible:
def Shoeless(Shoes.app):
self.t = para("Not clicked!")
def on_click_func(self):
alert("You clicked the button!")
self.t.replace("clicked!")
b = button("The label", click=self.on_click_func)
No where near as clean, and wouldn't be nearly as flexible, and I'm not even sure if it would be implementable.
Using decorators seems like an interesting way to map blocks of code to a specific action:
class BaseControl:
def __init__(self):
self.func = None
def clicked(self, func):
self.func = func
def __call__(self):
if self.func is not None:
self.func()
class Button(BaseControl):
pass
class Label(BaseControl):
pass
# The actual applications code (that the end-user would write)
class MyApp:
ok = Button()
la = Label()
#ok.clicked
def clickeryHappened():
print "OK Clicked!"
if __name__ == '__main__':
a = MyApp()
a.ok() # trigger the clicked action
Basically the decorator function stores the function, then when the action occurred (say, a click) the appropriate function would be executed.
The scope of various stuff (say, the la label in the above example) could be rather complicated, but it seems doable in a fairly neat manner..
You could actually pull this off, but it would require using metaclasses, which are deep magic (there be dragons). If you want an intro to metaclasses, there's a series of articles from IBM which manage to introduce the ideas without melting your brain.
The source code from an ORM like SQLObject might help, too, since it uses this same kind of declarative syntax.
I was never satisfied with David Mertz's articles at IBM on metaclsses so I recently wrote my own metaclass article. Enjoy.
This is extremely contrived and not pythonic at all, but here's my attempt at a semi-literal translation using the new "with" statement.
with Shoes():
t = Para("Not clicked!")
with Button("The Label"):
Alert("You clicked the button!")
t.replace("Clicked!")
The hardest part is dealing with the fact that python will not give us anonymous functions with more than one statement in them. To get around that, we could create a list of commands and run through those...
Anyway, here's the backend code I ran this with:
context = None
class Nestable(object):
def __init__(self,caption=None):
self.caption = caption
self.things = []
global context
if context:
context.add(self)
def __enter__(self):
global context
self.parent = context
context = self
def __exit__(self, type, value, traceback):
global context
context = self.parent
def add(self,thing):
self.things.append(thing)
print "Adding a %s to %s" % (thing,self)
def __str__(self):
return "%s(%s)" % (self.__class__.__name__, self.caption)
class Shoes(Nestable):
pass
class Button(Nestable):
pass
class Alert(Nestable):
pass
class Para(Nestable):
def replace(self,caption):
Command(self,"replace",caption)
class Command(Nestable):
def __init__(self, target, command, caption):
self.command = command
self.target = target
Nestable.__init__(self,caption)
def __str__(self):
return "Command(%s text of %s with \"%s\")" % (self.command, self.target, self.caption)
def execute(self):
self.target.caption = self.caption
## All you need is this class:
class MainWindow(Window):
my_button = Button('Click Me')
my_paragraph = Text('This is the text you wish to place')
my_alert = AlertBox('What what what!!!')
#my_button.clicked
def my_button_clicked(self, button, event):
self.my_paragraph.text.append('And now you clicked on it, the button that is.')
#my_paragraph.text.changed
def my_paragraph_text_changed(self, text, event):
self.button.text = 'No more clicks!'
#my_button.text.changed
def my_button_text_changed(self, text, event):
self.my_alert.show()
## The Style class is automatically gnerated by the framework
## but you can override it by defining it in the class:
##
## class MainWindow(Window):
## class Style:
## my_blah = {'style-info': 'value'}
##
## or like you see below:
class Style:
my_button = {
'background-color': '#ccc',
'font-size': '14px'}
my_paragraph = {
'background-color': '#fff',
'color': '#000',
'font-size': '14px',
'border': '1px solid black',
'border-radius': '3px'}
MainWindow.Style = Style
## The layout class is automatically generated
## by the framework but you can override it by defining it
## in the class, same as the Style class above, or by
## defining it like this:
class MainLayout(Layout):
def __init__(self, style):
# It takes the custom or automatically generated style class upon instantiation
style.window.pack(HBox().pack(style.my_paragraph, style.my_button))
MainWindow.Layout = MainLayout
if __name__ == '__main__':
run(App(main=MainWindow))
It would be relatively easy to do in python with a bit of that metaclass python magic know how. Which I have. And a knowledge of PyGTK. Which I also have. Gets ideas?
With some Metaclass magic to keep the ordering I have the following working. I'm not sure how pythonic it is but it is good fun for creating simple things.
class w(Wndw):
title='Hello World'
class txt(Txt): # either a new class
text='Insert name here'
lbl=Lbl(text='Hello') # or an instance
class greet(Bbt):
text='Greet'
def click(self): #on_click method
self.frame.lbl.text='Hello %s.'%self.frame.txt.text
app=w()
The only attempt to do this that I know of is Hans Nowak's Wax (which is unfortunately dead).
The closest you can get to rubyish blocks is the with statement from pep343:
http://www.python.org/dev/peps/pep-0343/
If you use PyGTK with glade and this glade wrapper, then PyGTK actually becomes somewhat pythonic. A little at least.
Basically, you create the GUI layout in Glade. You also specify event callbacks in glade. Then you write a class for your window like this:
class MyWindow(GladeWrapper):
GladeWrapper.__init__(self, "my_glade_file.xml", "mainWindow")
self.GtkWindow.show()
def button_click_event (self, *args):
self.button1.set_label("CLICKED")
Here, I'm assuming that I have a GTK Button somewhere called button1 and that I specified button_click_event as the clicked callback. The glade wrapper takes a lot of effort out of event mapping.
If I were to design a Pythonic GUI library, I would support something similar, to aid rapid development. The only difference is that I would ensure that the widgets have a more pythonic interface too. The current PyGTK classes seem very C to me, except that I use foo.bar(...) instead of bar(foo, ...) though I'm not sure exactly what I'd do differently. Probably allow for a Django models style declarative means of specifying widgets and events in code and allowing you to access data though iterators (where it makes sense, eg widget lists perhaps), though I haven't really thought about it.
Maybe not as slick as the Ruby version, but how about something like this:
from Boots import App, Para, Button, alert
def Shoeless(App):
t = Para(text = 'Not Clicked')
b = Button(label = 'The label')
def on_b_clicked(self):
alert('You clicked the button!')
self.t.text = 'Clicked!'
Like Justin said, to implement this you would need to use a custom metaclass on class App, and a bunch of properties on Para and Button. This actually wouldn't be too hard.
The problem you run into next is: how do you keep track of the order that things appear in the class definition? In Python 2.x, there is no way to know if t should be above b or the other way around, since you receive the contents of the class definition as a python dict.
However, in Python 3.0 metaclasses are being changed in a couple of (minor) ways. One of them is the __prepare__ method, which allows you to supply your own custom dictionary-like object to be used instead -- this means you'll be able to track the order in which items are defined, and position them accordingly in the window.
This could be an oversimplification, i don't think it would be a good idea to try to make a general purpose ui library this way. On the other hand you could use this approach (metaclasses and friends) to simplify the definition of certain classes of user interfaces for an existing ui library and depending of the application that could actually save you a significant amount of time and code lines.
I have this same problem. I wan to to create a wrapper around any GUI toolkit for Python that is easy to use, and inspired by Shoes, but needs to be a OOP approach (against ruby blocks).
More information in: http://wiki.alcidesfonseca.com/blog/python-universal-gui-revisited
Anyone's welcome to join the project.
If you really want to code UI, you could try to get something similar to django's ORM; sth like this to get a simple help browser:
class MyWindow(Window):
class VBox:
entry = Entry()
bigtext = TextView()
def on_entry_accepted(text):
bigtext.value = eval(text).__doc__
The idea would be to interpret some containers (like windows) as simple classes, some containers (like tables, v/hboxes) recognized by object names, and simple widgets as objects.
I dont think one would have to name all containers inside a window, so some shortcuts (like old-style classes being recognized as widgets by names) would be desirable.
About the order of elements: in MyWindow above you don't have to track this (window is conceptually a one-slot container). In other containers you can try to keep track of the order assuming that each widget constructor have access to some global widget list. This is how it is done in django (AFAIK).
Few hacks here, few tweaks there... There are still few things to think of, but I believe it is possible... and usable, as long as you don't build complicated UIs.
However I am pretty happy with PyGTK+Glade. UI is just kind of data for me and it should be treated as data. There's just too much parameters to tweak (like spacing in different places) and it is better to manage that using a GUI tool. Therefore I build my UI in glade, save as xml and parse using gtk.glade.XML().
Personally, I would try to implement JQuery like API in a GUI framework.
class MyWindow(Window):
contents = (
para('Hello World!'),
button('Click Me', id='ok'),
para('Epilog'),
)
def __init__(self):
self['#ok'].click(self.message)
self['para'].hover(self.blend_in, self.blend_out)
def message(self):
print 'You clicked!'
def blend_in(self, object):
object.background = '#333333'
def blend_out(self, object):
object.background = 'WindowBackground'
Here's an approach that goes about GUI definitions a bit differently using class-based meta-programming rather than inheritance.
This is largley Django/SQLAlchemy inspired in that it is heavily based on meta-programming and separates your GUI code from your "code code". I also think it should make heavy use of layout managers like Java does because when you're dropping code, no one wants to constantly tweak pixel alignment. I also think it would be cool if we could have CSS-like properties.
Here is a rough brainstormed example that will show a column with a label on top, then a text box, then a button to click on the bottom which shows a message.
from happygui.controls import *
MAIN_WINDOW = Window(width="500px", height="350px",
my_layout=ColumnLayout(padding="10px",
my_label=Label(text="What's your name kiddo?", bold=True, align="center"),
my_edit=EditBox(placeholder=""),
my_btn=Button(text="CLICK ME!", on_click=Handler('module.file.btn_clicked')),
),
)
MAIN_WINDOW.show()
def btn_clicked(sender): # could easily be in a handlers.py file
name = MAIN_WINDOW.my_layout.my_edit.text
# same thing: name = sender.parent.my_edit.text
# best practice, immune to structure change: MAIN_WINDOW.find('my_edit').text
MessageBox("Your name is '%s'" % ()).show(modal=True)
One cool thing to notice is the way you can reference the input of my_edit by saying MAIN_WINDOW.my_layout.my_edit.text. In the declaration for the window, I think it's important to be able to arbitrarily name controls in the function kwargs.
Here is the same app only using absolute positioning (the controls will appear in different places because we're not using a fancy layout manager):
from happygui.controls import *
MAIN_WINDOW = Window(width="500px", height="350px",
my_label=Label(text="What's your name kiddo?", bold=True, align="center", x="10px", y="10px", width="300px", height="100px"),
my_edit=EditBox(placeholder="", x="10px", y="110px", width="300px", height="100px"),
my_btn=Button(text="CLICK ME!", on_click=Handler('module.file.btn_clicked'), x="10px", y="210px", width="300px", height="100px"),
)
MAIN_WINDOW.show()
def btn_clicked(sender): # could easily be in a handlers.py file
name = MAIN_WINDOW.my_edit.text
# same thing: name = sender.parent.my_edit.text
# best practice, immune to structure change: MAIN_WINDOW.find('my_edit').text
MessageBox("Your name is '%s'" % ()).show(modal=True)
I'm not entirely sure yet if this is a super great approach, but I definitely think it's on the right path. I don't have time to explore this idea more, but if someone took this up as a project, I would love them.
Declarative is not necessarily more (or less) pythonic than functional IMHO. I think a layered approach would be the best (from buttom up):
A native layer that accepts and returns python data types.
A functional dynamic layer.
One or more declarative/object-oriented layers.
Similar to Elixir + SQLAlchemy.

Categories

Resources