Is there a way to use ribbon toolbars in Tkinter? - python

I am yet to decide what language and tools to use for my next project. I would love to use python, but I would like to implement ribbon toolbars. Some work has been done in Tk (http://www.ellogon.org/petasis/bibliography/Tcl2010/TkRibbon.pdf), but it looks like it hasn't been implemented in tkinter yet. Is there anything I can do to get this to work?

You need to create a wrapper for this and get a version of the binary you can use. I built this for use with Python 3.4 and copied it to tkribbon1.0-x86_64.zip. You should unzip this in the Python/tcl subdirectory so the version of tcl used by python can load it.
A minimal wrapper looks like this:
from tkinter import Widget
from os import path
class Ribbon(Widget):
def __init__(self, master, kw=None):
self.version = master.tk.call('package','require','tkribbon')
self.library = master.tk.eval('set ::tkribbon::library')
Widget.__init__(self, master, 'tkribbon::ribbon', kw=kw)
def load_resource(self, resource_file, resource_name='APPLICATION_RIBBON'):
"""Load the ribbon definition from resources.
Ribbon markup is compiled using the uicc compiler and the resource included
in a dll. Load from the provided file."""
self.tk.call(self._w, 'load_resources', resource_file)
self.tk.call(self._w, 'load_ui', resource_file, resource_name)
if __name__ == '__main__':
import sys
from tkinter import *
def main():
root = Tk()
r = Ribbon(root)
name = 'APPLICATION_RIBBON'
if len(sys.argv) > 1:
resource = sys.argv[1]
if len(sys.argv) > 2:
name = sys.argv[2]
else:
resource = path.join(r.library, 'libtkribbon1.0.dll')
r.load_resource(resource, name)
t = Text(root)
r.grid(sticky=(N,E,S,W))
t.grid(sticky=(N,E,S,W))
root.grid_columnconfigure(0, weight=1)
root.grid_rowconfigure(1, weight=1)
root.mainloop()
main()
Running this uses the resources built-in to the tkribbon dll and looks like . The complicated bit is going to be getting some Ribbon markup resources into a DLL for loading.
You can use this example to load ribbons from existing applications. For instance, python Ribbon.py c:\Windows\System32\mspaint.exe MSPAINT_RIBBON will load up the ribbon resource from mspaint. The resource name in this case has to be included as the default is APPLICATION_RIBBON. For your own ribbon, using uicc to build a .rc file, then rc /r file.rc to produce a .res file and finally link -dll -out:file.dll file.rc -noentry -machine:AMD64 seems to work to produce a resource only DLL that works with this extension.

Related

Python Logging - only show logs from my libraries

I have a relatively complex ecosystem of applications and libraries that are scheduled to run in my environment.
I am trying to improve my logging and in particular I'd like to write debug information to a logging file, and I'd like that log to contain the logger.debug("string") lines from all the imported libraries I wrote, but not from libraries I import from pypi.
example:
import sys
import numpy
from bs4 import BeautifulSoup
import logging
import mylibrary
import myotherlibrary
logger = logging.getLogger(application_name) # I don't use _ _ name _ _ in all of them, but I can change this line as necessary
so in this case when I set logger level to debug, I'd like to see debug information from the current script, from mylibrary and from myotherlibrary , but not from bs4,numpy, etc.
bonus: Ideally I would like to not have to hardcode every time the name of the libraries, but just have the script "know" it (from naming convention maybe?)
If anyone has any ideas it'd be greatly appreciated!
Python doesn't really have a concept of "libraries I wrote" vs "libraries imported with pypi" - a library is a library unfortunately.
However, depending on how your libraries are set up, you may be able to get a realllly hacky custom logger?
By default, Python libraries installed with pip go to a central location - usually something like /usr/local/lib or %APPDATA% on windows. In contrast, local libraries are usually within the same directory as the calling script. We can use this to our advantage!
The following code demonstrates a kinda proof-of-concept - I've left a few methods needing implementing as an exercise ;)
#CustomLogger.py
import __main__
import logging
import os
#create a custom log class, inheriting the current logger class
class CustomLogger(logging.getLoggerClass()):
custom_lib = False
def __init__(self, name):
#initialise the base logger
super().__init__(name)
#get the directory we are being run from
current_dir = os.path.dirname(__main__.__file__)
permutations = ['/', '.py', '.pyc']
#check if we are a custom library, or if we are one installed via pip etc
self.custom_lib = self.checkExists(current_dir, permutations)
self.propagate = not self.custom_lib
def checkExists(self, current_dir, permutations):
#loop through each permutation and see if a file matching that spec exists
#currently looks for .py/.pyc files and directories
for perm in permutations:
file = os.path.join(current_dir, self.name + perm)
if os.path.exists(file):
return True
return False
def isEnabledFor(self, level):
if self.custom_lib:
return super().isEnabledFor(level)
return False
#the hackiest part :)
#these are two sample overrides that only log if we're a custom
#library (i.e. one we've written, not installed)
#there are a few more methods that I've not implemented, a full
#list is available at https://docs.python.org/3/library/logging.html#logging.Logger
def debug(self, msg, *args, **kwargs):
if self.custom_lib:
return super().debug(msg, args, kwargs)
def info(self, msg, *args, **kwargs):
if self.custom_lib:
return super().info(msg, args, kwargs)
#most important part - also override the logger class
#this means that any calls to logging.getLogger() will use our new subclass
logging.setLoggerClass(CustomLogger)
You could then use it like this:
import CustomLogger #needs importing first so it ensures the logger is setup
import sys
import numpy
from bs4 import BeautifulSoup
import logging
import mylibrary
import myotherlibrary
logger = logging.getLogger(application_name) #returns type CustomLogger

How to get Class diagram from Python source code?

I try to get a class diagram from Python source code in Client folder with pyreverse but it requires __init__.py
(venv) C:\Users\User\Desktop\project> pyreverse Client
parsing Client\__init__.py...
Failed to import module Client\__init__.py with error:
No module named Client\__init__.py.
I don't find any solution for this. Is there a way to get the diagram?
Update:
There are many files in Client folder:
Client.py
GUI.py
script.py
...
This is a part of the Client.py code:
import threading
class Client:
def __init__(self):
self.socket = None
self.listen_socket = None
self.buff_dict = {}
self.message_list_dict = {}
self.lock = threading.Lock()
self.target = None
self.listen_flag = True
This is a part of the GUI.py code:
import tkinter as tk
class Window(object):
def __init__(self, title, font, client):
self.title = title
self.font = font
self.client = client
self.root = tk.Tk()
self.root.title(title)
self.build_window()
def build_window(self):
pass
class LoginWindow(Window):
def __init__(self, client, font):
super(LoginWindow, self).__init__('Login', font, client)
self.build_window()
Thanks to #Anwarvic and #bruno, I came up with the solution for this.
Firstly, create empty __init__.py file inside Client folder:
(venv) C:\Users\User\Desktop\project\Client> type NUL > __init__.py
Then go to the parent folder of the Client folder where I want to get the class diagram:
(venv) C:\Users\User\Desktop\project> pyreverse Client -o png
But I got this error:
The output format 'png' is currently not available.
Please install 'Graphviz' to have other output formats than 'dot' or 'vcg'.
After some findings, I found this solution. Then I can run the pyreverse without any error.
This is the class diagram I got using pyreverse:
It seems like you don't have an __init__.py in the folder that contains Client.py. You should be able to just create the file without putting anything in it, as its main purpose is to indicate that the folder is a package.
See this SO question about __init__.py for a more in-depth explanation about the file.
The most convenient approach should be to have a Jupyter notebook extension that dynamically generates diagrams of the classes defined in a notebook somehow like the variable inspector - possibly with the option to specify an alternative root like datetime in the first answer to
creating UML charts by Pylint/pyreverse within Jupyter labs / console.
Note: This would remove the restriction of pyreverse requiring the displayed classes to be part of a module.

How to set the default open path for a Gtk.FileChooserWidget?

If I set the current folder via the method Gtk.FileChooserWidget.set_current_folder(), the first time I open the file chooser, it opens on the location used as argument for set_current_folder()
But, if I select a file, the I re-open the file-chooser, it opens on the "most_recent_used_files".
I'd like it opens on the last selected file's folder path.
How to do it?
Thank you.
From the docs:
Old versions of the file chooser's documentation suggested using gtk_file_chooser_set_current_folder() in various situations, with the intention of letting the application suggest a reasonable default folder. This is no longer considered to be a good policy, as now the file chooser is able to make good suggestions on its own. In general, you should only cause the file chooser to show a specific folder when it is appropriate to use gtk_file_chooser_set_filename() - i.e. when you are doing a File/Save As command and you already have a file saved somewhere.
You may or may not like the reasoning for this behavior. If you're curious about how it came about, see File chooser recent-files in the mailing list and Help the user choose a place to put a new file on the GNOME wiki.
Setting the current folder each time works for me, but it is a little tricky. I'm using Gtk 3.14 and Python 2.7.
You have to get the filename before resetting the directory, or it's lost, and the current directory may be None, so you have to check for that.
This code is tested on Debian jessie and Windows 7.
import os.path as osp
from gi.repository import Gtk
class FileDialog(Gtk.FileChooserDialog):
def __init__(self, parent, title):
Gtk.FileChooserDialog.__init__(self, title, parent)
self.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)
self.add_button(Gtk.STOCK_OPEN, Gtk.ResponseType.OK)
self.set_current_folder(osp.abspath('.'))
def __call__(self):
resp = self.run()
self.hide()
fname = self.get_filename()
d = self.get_current_folder()
if d:
self.set_current_folder(d)
if resp == Gtk.ResponseType.OK:
return fname
else:
return None
class TheApp(Gtk.Window):
def on_clicked(self, w, dlg):
fname = dlg()
print fname if fname else 'canceled'
def __init__(self):
Gtk.Window.__init__(self)
self.connect('delete_event', Gtk.main_quit)
self.set_resizable(False)
dlg = FileDialog(self, 'Your File Dialog, Sir.')
btn = Gtk.Button.new_with_label('click here')
btn.connect('clicked', self.on_clicked, dlg)
self.add(btn)
btn.show()
if __name__ == '__main__':
app = TheApp()
app.show()
Gtk.main()

How do I call a plugin module that's loaded?

Either it's lack of sleep but I feel silly that I can't get this. I have a plugin, I see it get loaded but I can't instantiate it in my main file:
from transformers.FOMIBaseClass import find_plugins, register
find_plugins()
Here's my FOMIBaseClass:
from PluginBase import MountPoint
import sys
import os
class FOMIBaseClass(object):
__metaclass__ = MountPoint
def __init__(self):
pass
def init_plugins(self):
pass
def find_plugins():
plugin_dir = os.path.dirname(os.path.realpath(__file__))
plugin_files = [x[:-3] for x in os.listdir(plugin_dir) if x.endswith("Transformer.py")]
sys.path.insert(0, plugin_dir)
for plugin in plugin_files:
mod = __import__(plugin)
Here's my MountPoint:
class MountPoint(type):
def __init__(cls,name,bases,attrs):
if not hasattr(cls,'plugins'):
cls.plugins = []
else:
cls.plugins.append(cls)
I see it being loaded:
# /Users/carlos/Desktop/ws_working_folder/python/transformers/SctyDistTransformer.pyc matches /Users/carlos/Desktop/ws_working_folder/python/transformers/SctyDistTransformer.py
import SctyDistTransformer # precompiled from /Users/carlos/Desktop/ws_working_folder/python/transformers/SctyDistTransformer.pyc
But, for the life of me, I can't instantiate the 'SctyDistTransformer' module from the main file. I know I'm missing something trivial. Basically, I want to employ a class loading plugin.
To dymically load Python modules from arbitrary folders use imp module:
http://docs.python.org/library/imp.html
Specifically the code should look like:
mod = imp.load_source("MyModule", "MyModule.py")
clz = getattr(mod, "MyClassName")
Also if you are building serious plug-in architecture I recommend using Python eggs and entry points:
http://wiki.pylonshq.com/display/pylonscookbook/Using+Entry+Points+to+Write+Plugins
https://github.com/miohtama/vvv/blob/master/vvv/main.py#L104

Show Explorer's properties dialog for a file in Windows

Is there an easy way to show the properties dialog for a file in Windows using Python?
I'm trying to show the same window that pops up when you right click a file in Explorer and choose Properties.
The way to do this is to call the Windows ShellExecuteEx() API passing the properties verb. There are various high-level Python wrappers of this but I have not succeeded in getting any of them to work with the properties verb. Instead I would use good old ctypes.
import time
import ctypes
import ctypes.wintypes
SEE_MASK_NOCLOSEPROCESS = 0x00000040
SEE_MASK_INVOKEIDLIST = 0x0000000C
class SHELLEXECUTEINFO(ctypes.Structure):
_fields_ = (
("cbSize",ctypes.wintypes.DWORD),
("fMask",ctypes.c_ulong),
("hwnd",ctypes.wintypes.HANDLE),
("lpVerb",ctypes.c_char_p),
("lpFile",ctypes.c_char_p),
("lpParameters",ctypes.c_char_p),
("lpDirectory",ctypes.c_char_p),
("nShow",ctypes.c_int),
("hInstApp",ctypes.wintypes.HINSTANCE),
("lpIDList",ctypes.c_void_p),
("lpClass",ctypes.c_char_p),
("hKeyClass",ctypes.wintypes.HKEY),
("dwHotKey",ctypes.wintypes.DWORD),
("hIconOrMonitor",ctypes.wintypes.HANDLE),
("hProcess",ctypes.wintypes.HANDLE),
)
ShellExecuteEx = ctypes.windll.shell32.ShellExecuteEx
ShellExecuteEx.restype = ctypes.wintypes.BOOL
sei = SHELLEXECUTEINFO()
sei.cbSize = ctypes.sizeof(sei)
sei.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_INVOKEIDLIST
sei.lpVerb = "properties"
sei.lpFile = "C:\\Desktop\\test.txt"
sei.nShow = 1
ShellExecuteEx(ctypes.byref(sei))
time.sleep(5)
The reason I put in the call to sleep is that the properties dialog is shown as a window in the calling process. If the Python executable terminates immediately following the call to ShellExecuteEx, then there is nothing there to service the dialog and it does not show.

Categories