How to use a string for icon bitmap? - python

Is there a way to use a string for the iconbitmap in the Tkinter (Python 2.7.9) module?
I know that you can prodive a file path (even though I haven't understood where the difference between default and bitmap as parameters is.
The reason I am asking is because I want to create out of a Python script an .exe with py2exe (which works), but I would need to create a icon file then to be able to use an icon.
Any workaround or other method is appreciated.

(Note to folks using Python 3, see my supplemental answer for an alternative that only works in that version.)
I don't know of any way to pass iconbitmap() anything other than a file path in Python 2.x, so here's a workaround that creates a temporary file from a string representation of icon file's contents to pass it. It also shows a way to ensure the temporary file gets deleted.
import atexit
import binascii
import os
import tempfile
try:
import Tkinter as tk
except ModuleNotFoundError: # Python 3
import tkinter as tk
iconhexdata = '00000100010010100000010018006803000016000000280000001000000020' \
'000000010018000000000040030000130b0000130b00000000000000000000' \
'ffffff6c6c6d6c6c6d6c6c6d6c6c6d6c6c6d6c6c6d6c6c6d6c6c6d6c6c6d6c' \
'6c6d6c6c6d6c6c6d6c6c6d6c6c6dffffffffffff6c6c6d6c6c6d6c6c6d6c6c' \
'6d6c6c6d6c6c6d6c6c6d6c6c6d6c6c6d6c6c6d6c6c6d6c6c6d6c6c6d6c6c6d' \
'ffffffffffff6c6c6d6c6c6dffffffffffffffffffffffffffffffffffffff' \
'ffffffffffffffffffffff6c6c6d6c6c6dffffffffffff6c6c6d6c6c6dffff' \
'ff6c6c6d6c6c6d6c6c6d6c6c6d6c6c6d6c6c6d6c6c6d6c6c6dffffff6c6c6d' \
'6c6c6dffffffffffff6c6c6d6c6c6dffffff6c6c6d6c6c6d6c6c6d6c6c6d6c' \
'6c6d6c6c6d6c6c6d6c6c6dffffff6c6c6d6c6c6dffffffffffff6c6c6d6c6c' \
'6dfffffffffffffffffffffffffffffff2f4f7d6dfe9b8cadb95b2cfedf2f6' \
'6c6c6d6c6c6dfffffffffffffffffffffffff4f7fac0d4e69bb9d6739dc657' \
'89ba3e78b03f78af4177ad4276abd2deeaffffffffffffffffffffffffffff' \
'ffffffffdfe9f24178ad4178ad4178ad5081b17398be9db8d3bed4e6bbd7ec' \
'add7f3fffffffffffffffffffffffffffffffffffff8fafcaac2dac4d3e4df' \
'e8f1f9fbfdfffffff4fafd91cff520a3f10297eee4f4feffffffffffffffff' \
'ffffffffffffffffffffffffffffffffffffffe7f4fd7fcaf6159def0595ec' \
'179fec82c7f4bad6f7fdfefffffffffffffffffffffffffffffffffdfeffdb' \
'f0fd7bc8f6119bed0695eb1a9ded7ecaf5f0f8febfd3f73165e495b1f1ffff' \
'fffffffffffffffffffffffffff6fbfe2fa6ee0695eb1b9eed86ccf5e8f6fd' \
'ffffffd2dff93468e5326ae5c7d6f8ffffffffffffffffffffffffffffffff' \
'ffff96d2f784cbf5eaf6fdffffffffffffe3eafb4275e72c66e4b6caf6ffff' \
'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff' \
'f3f6fd5784ea2c66e499b5f2ffffffffffffffffffffffffffffffffffffff' \
'fffffffffffffffffffffffffffffdfeff7097ed2c66e47a9eeeffffffffff' \
'fffffffffffffffffffffffffffffffffffffffffffffffffffffffffdfeff' \
'85a7ef2c66e4608cebf9fbfeffffffffffffffffffffffff00000000000000' \
'00000000000000000000000000000000000000000000000000000000000000' \
'0000000000000000000000000000000000000000000000000000'
def on_closing(iconfile):
try:
os.remove(iconfile.name)
except Exception:
pass
with tempfile.NamedTemporaryFile(delete=False) as iconfile:
iconfile.write(binascii.a2b_hex(iconhexdata))
# Register a clean-up function.
atexit.register(lambda file=iconfile: on_closing(file))
root = tk.Tk()
root.title('stackoverflow!')
root.iconbitmap(iconfile.name)
tk.Label(root, text='Note the custom icon').pack()
tk.Button(root, text='OK', bg='lightgreen', command=root.quit).pack()
root.mainloop()
The window displayed will have a custom icon as shown below:
You didn't ask how to do it, but here's the code I used to convert the original .ico file into the Python string variable used in my example:
from __future__ import print_function
import binascii
try:
from itertools import izip_longest as zip_longest
except ImportError:
from itertools import zip_longest
iconfile = 'stackoverflow.ico' # Path to icon file.
VAR_NAME = 'iconhexdata'
VAR_SUFFIX = ' = '
INDENTATION = ' ' * len(VAR_NAME+VAR_SUFFIX)
MAX_LINE_LENGTH = 80
EXTRA_CHARS = '"" \\' # That get added to each group of hex digits.
LINE_LENGTH = MAX_LINE_LENGTH - len(INDENTATION) - len(EXTRA_CHARS)
def grouper(chunk_size, iterable):
""" Collect data into fixed-length chunks or blocks.
s -> (s0,s1,...sn-1), (sn,sn+1,...s2n-1), (s2n,s2n+1,...s3n-1), ...
"""
return zip_longest(*[iter(iterable)]*chunk_size, fillvalue='')
with open(iconfile, 'rb') as imgfile:
hexstr = [chr(x) for x in bytearray(binascii.b2a_hex(imgfile.read()))]
hexlines = (''.join(str(x) for x in group) for group in grouper(LINE_LENGTH, hexstr))
print(VAR_NAME + VAR_SUFFIX, end='')
print((' \\\n' + INDENTATION).join((repr(line) for line in hexlines)))

This doesn't answer your Python 2.7 question, but may be of interest to others nowadays who are using Python 3. In the later version of Python the tkinter module has an alternative to the iconbitmap() function—named iconphoto()—that can be used to set the title-bar icon of any tkinter/toplevel window. However, unlike the former method, the image should be the instance of the tkinter.PhotoImage class, not only a file path string — and there are ways to create a PhotoImage object from a byte string in the program so there would be no need to use a file at all, even a temporary one as was the case in my original answer.
#!/usr/bin/env python3
iconimgdata = b'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAAGXRFWHRTb2Z0d' \
b'2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAXpJREFUeNpi+I8V/Pv7fnnrkz' \
b'Sd1z0J/37/RJZhYsAKGJkEwis4DV1+3jrzYXEjsgwODRA98c1sctrfTmz6vG0' \
b'WQhzNLd8vTft6uuXvt1cQ7u9Xj5+XOgHd9u3UNogIioZ/v7+9W6b/eirb23nS' \
b'X8+0/f32Aij48/6lpxkmT7OMflw7ju4HRmZ2fq813MalDH+/fTvZ8GG50bfT9' \
b'aySckLZ3f9///95+xxIDcgWDPDv64uf12b8vDzt748PDFxCHBrZzPwWHBrOQI' \
b'8hNPz9/fPeiU1cglK8otI8wlJMLGz/fn/9cXXunyv9f788Eoh9xMgtDVTGAjf' \
b'12/vnl7dNh7BZOPl5xZVFFbSEZXTZTGazM3yCqEZx0u8fX9/cPfPh6e0PT258' \
b'efMEqP8/A+O//0z//jPaZ0wQVdRFaMjLzQWyJk2ejOyNH18/f3r95NPrR19e3' \
b'FV3iCivqoeoYUFWBNGJCSb5ChER0zgAig1oriKgAZd70ADJTgIIMACVtvtL6F' \
b'X2cAAAAABJRU5ErkJggg=='
import base64
import tkinter as tk # Python 3
root = tk.Tk()
root.title('stackoverflow!')
root.geometry('225x50')
img = base64.b64decode(iconimgdata)
photo = tk.PhotoImage(data=img)
root.iconphoto(False, photo)
tk.Label(root, text='Note the custom icon').pack()
tk.Button(root, text='OK', bg='lightgreen', command=root.quit).pack()
root.mainloop()
Here's what the demo looks like running:
Here again is the code I used to convert the 16x16 pixel .png image file I had into the Python bytestring variable used in the code in my example above. Here's a link to the small stackoverflow.png image file being used.
#!/usr/bin/env python3
import base64
from itertools import zip_longest
imgfilepath = 'stackoverflow.png' # Path to an image file.
VAR_NAME = 'iconimgdata'
VAR_SUFFIX = ' = '
INDENTATION = ' ' * len(VAR_NAME+VAR_SUFFIX)
MAX_LINE_LENGTH = 79
EXTRA_CHARS = '"" \\' # That get added to each group of hex digits.
LINE_LENGTH = MAX_LINE_LENGTH - len(INDENTATION) - len(EXTRA_CHARS)
def grouper(chunk_size, iterable):
""" Collect data into fixed-length chunks or blocks.
s -> (s0,s1,...sn-1), (sn,sn+1,...s2n-1), (s2n,s2n+1,...s3n-1), ...
"""
return zip_longest(*[iter(iterable)]*chunk_size, fillvalue='')
with open(imgfilepath, 'rb') as file:
hexstr = [chr(x) for x in base64.b64encode(file.read())]
hexlines = (''.join(str(x) for x in group) for group in grouper(LINE_LENGTH, hexstr))
print(VAR_NAME + VAR_SUFFIX + 'b', end='')
print((' \\\n' + INDENTATION + 'b').join((repr(line) for line in hexlines)))

Related

Filename has weird encoding when saving image

I'm webscraping, and when I'm saving the image, it seems like the encoding is changed when saving the file. For instance, in the filename, 'é' becomes '%c3%a9'. I'm capable of catching all these changes with this function:
def unify_filename(string):
return string.lower().strip().replace(',', '%2c').replace('%', '%25').replace('ô', '%b3%b4'). \
replace('é', '%c3%a9').replace('++', '+').replace('è', '%c3%a8').replace('î', '%c3%ae'). \
replace('#', '%23').replace(';', '%3b').replace('%2b%2b', '%2b'). \
replace('&', '%26').replace('+', '%2b').replace(' ', '+').replace('[', '%5b'). \
replace(']', '%5d').replace('%2b', '+').replace('%40', '#').replace('®', '%c2%ae'). \
replace('%7e', '').replace('~', '').replace('%27', '').replace('©', '%c2%a9'). \
replace("'", '').replace('ô', '%b3%b4').replace('+', ' ').replace('^', '%5e'). \
replace('$', '%24').replace(' ', ' ').replace('`', '%60').replace('’', '%e2%80%99')
Is there an easier way? Is this some encoding I don't know?
You can handle these values using urllib parser. you can use the unquote() function.
Use the following piece of code:
import urllib.parse
print(urllib.parse.unquote('%c3%a9', encoding='utf-8'))
Output:
'é'

Python tkinter print result can't complete

import filecmp
import os
import tkinter as tk
import tkinter.font as tkFont
from tkinter import *
def gg():
disk = (number_entry.get())
if disk == "2" :
DIRS = [r'C:\Newtest\1', r'C:\Newtest\2', r'C:\Newtest\3',r'C:\Newtest\4',r'C:\Newtest\5',r'C:\Newtest\6',r'C:\Newtest\7',r'C:\Newtest\8',r'C:\Newtest\9',r'C:\Newtest\10']
FILES = [('copy1.txt', 'copy2.txt'), ('fcmp1.txt', 'fcmp2.txt'), ('filecp1.txt', 'filecp2.txt')]
for e, dir in enumerate(DIRS, 1):
cmp = True
for file in FILES:
try:
if not filecmp.cmp(os.path.join(dir, file[0]), os.path.join(dir, file[1])):
cmp = False
break
except Exception as ex:
print(f'Error -> {ex}', file=sys.stderr)
continue
x = (f'E_{e} compare {"pass" if cmp else "fail"}')
print(x)
result_label.configure(text=x,wraplength=300,font=fontExample3)
DIRS = [r'C:\Newtest\1', r'C:\Newtest\2', r'C:\Newtest\3',r'C:\Newtest\4',r'C:\Newtest\5',r'C:\Newtest\6',r'C:\Newtest\7',r'C:\Newtest\8',r'C:\Newtest\9',r'C:\Newtest\10']
FILES = [('copy1.txt', 'copy2.txt'), ('fcmp1.txt', 'fcmp2.txt'), ('filecp1.txt', 'filecp2.txt')]
for e, dir in enumerate(DIRS, 1):
cmp = True
window = tk.Tk()
window.title('XXX')
window.geometry('1200x700')
window.configure(background='Azure')
fontExample = tkFont.Font(family="Segoe UI", size=20)
fontExample2 = tkFont.Font(family="Segoe UI", size=10)
fontExample3 = tkFont.Font(family="Segoe UI", size=15)
header_label = tk.Label(window, text='XXXX',font=fontExample)
header_label.pack()
number_frame = tk.Frame(window)
number_frame.pack(side=tk.TOP)
number_label = tk.Label(number_frame, text='Number of disks',font=fontExample2)
number_label.pack(side=tk.LEFT)
number_entry = tk.Entry(number_frame)
number_entry.pack(side=tk.LEFT)
result_label = tk.Label(window)
result_label.pack()
calculate_btn = tk.Button(window, text='First calculate', command=gg,font=fontExample3)
calculate_btn.pack()
window.mainloop()
Dear All ,
I have the question in the line:
result_label.configure(text=x,wraplength=300,font=fontExample3)
I want the result can print complete like my console.
Consele result is :
E_1 compare pass
E_2 compare fail
E_3 compare pass
E_4 compare pass
E_5 compare pass
E_6 compare pass
E_7 compare pass
E_8 compare pass
E_9 compare pass
E_10 compare fail
But in the tkinter result is -> E_10 compare fail
Please tolerate me as a newbie in tkinter.
Thanks!
If you want to add to the text that is already on the label, you can do this:
result_label.configure(text=result_label['text'] + x + '\n')
result_label['text'] returns the current text of the widget (equivalent to result_label.cget('text'))
You also don't need to configure wraplength and font each time, just do it once when creating the label like this:
result_label = tk.Label(window, wraplenght=300, font=fontExample3)
Also:
I strongly advise against using wildcard (*) when importing something, You should either import what You need, e.g. from module import Class1, func_1, var_2 and so on or import the whole module: import module then You can also use an alias: import module as md or sth like that, the point is that don't import everything unless You actually know what You are doing; name clashes are the issue.
I strongly suggest following PEP 8 - Style Guide for Python Code. Function and variable names should be in snake_case, class names in CapitalCase. Have two blank lines around function and class declarations.

Why does Python 2.7 os.system(command) sometimes recurse in Windows 8 while os.startfile(command) does not recurse?

Why does Python 2.7 os.system(command) sometimes recurse in Windows 8 while os.startfile(command) does not recurse?
My command is backup.bat, which is a Windows batch file.
The contents of backup.bat are:
"C:\Users\Frank Chang\Anaconda2\python.exe" -m animation_mini
#echo off
echo %time%
timeout 10 > NUL
echo %time%
The way I discovered that the Python 2.7 animate function in animation_mini.py was being invoked multiple times when os.system
was used is to place a print statement at the beginning of the animate function entry point and count the print statements in the console.
I was told today that Python 2.7 os.system(command) is a wrapper around the C function execve. But that fact does not explain the recursion I see with os.system('backup.bat').
os.system is being called from adder.cgi, a Python 2.7 CGI script whose lines of code are:
#!C:\Users\Frank Chang\Anaconda2\python.exe
import cgitb
import cgi
import os
import signal
import threading , time
import sys
sys.path.insert(0,"C:\Users\Frank Chang\Documents\Arduino\mary\data\usr\lib\python2.7\dist-packages\HTMLgen")
import HTMLgen
import subprocess
import win32api
import pandas as pd
def main():
form = cgi.FieldStorage()
numStr1 = form.getfirst("input1", "0")
numStr2 = form.getfirst("input2", "0")
numStr3 = form.getfirst("input3", "0")
numStr4 = form.getfirst("input4", "0")
numStr5 = form.getfirst("input5", "0")
numStr6 = form.getfirst("input6", "0")
numStr7 = form.getfirst("input7", "0")
numStr8 = form.getfirst("input8", "0")
numStr9 = form.getfirst("input9", "0")
numStr10 = form.getfirst("input10", "0")
numStr11 = form.getfirst("input11", "0")
numStr12 = form.getfirst("input12", "0")
numStr13 = form.getfirst("input13", "0")
numStr14 = form.getfirst("input14", "0")
from pandas import ExcelWriter
writer = ExcelWriter('PythonExport.xlsx')
from pandas import DataFrame
yourdf = DataFrame({'DC Start': numStr1, 'DC Duration': numStr2, 'Plant Start': numStr3, 'Plant Duration': numStr4,
'Supplier Start': numStr5, 'Supplier Duration': numStr6}, index=[0])
yourdf.to_excel(writer,'Disruptions')
yourdf = DataFrame({'FGI': numStr10, 'WIP': numStr11, 'DC': numStr12, 'Plant': numStr13,
'Supplier' : numStr14}, index=[0])
yourdf.to_excel(writer,'Policy')
writer.save()
os.system('backup.bat')
def processInput(numStr1, numStr2):
'''Process input parameters and return the final page as a string.'''
num1 = int(numStr1) # transform input to output data
num2 = int(numStr2)
total = num1+num2
return str(total)
def fileToStr(fileName):
"""Return a string containing the contents of the named file."""
fin = open(fileName);
contents = fin.read();
fin.close()
return contents
main()
Might my CGI script be the cause of the os.system('backup.bat') recursion?
Both eryksun and Blckkngt had exactly the right answer yesterday about mixing multiple versions together to cause the recursive nightmare.
The solution to the INTERNAL ERROR: cannot create temporary directory lies in the pyinstaller which can be corrected through using win32api.SetDllDirectory(..) and copying the parent process's environment variables and adding the key value pair "TEMP", "C:/TMP" to the dictionary
The CGI script had no bearing on whether our animation function was run several times consecutively.

py2exe SytaxError: invalid syntax (asyncsupport.py, line22) [duplicate]

This command works fine on my personal computer but keeps giving me this error on my work PC. What could be going on? I can run the Char_Limits.py script directly in Powershell without a problem.
error: compiling 'C:\ProgramData\Anaconda2\lib\site-packages\jinja2\asyncsupport.py' failed
SyntaxError: invalid syntax (asyncsupport.py, line 22)
My setup.py file looks like:
from distutils.core import setup
import py2exe
setup (console=['Char_Limits.py'])
My file looks like:
import xlwings as xw
from win32com.client import constants as c
import win32api
"""
Important Notes: Header row has to be the first row. No columns without a header row. If you need/want a blank column, just place a random placeholder
header value in the first row.
Product_Article_Number column is used to determine the number of rows. It must be populated for every row.
"""
#functions, hooray!
def setRange(columnDict, columnHeader):
column = columnDict[columnHeader]
rngForFormatting = xw.Range((2,column), (bttm, column))
cellReference = xw.Range((2,column)).get_address(False, False)
return rngForFormatting, cellReference
def msg_box(message):
win32api.MessageBox(wb.app.hwnd, message)
#Character limits for fields in Hybris
CharLimits_Fields = {"alerts":500, "certifications":255, "productTitle":300,
"teaserText":450 , "includes":1000, "compliance":255, "disclaimers":9000,
"ecommDescription100":100, "ecommDescription240":240,
"internalKeyword":1000, "metaKeywords":1000, "metaDescription":1000,
"productFeatures":7500, "productLongDescription":1500,"requires":500,
"servicePlan":255, "skuDifferentiatorText":255, "storage":255,
"techDetailsAndRefs":12000, "warranty":1000}
# Fields for which a break tag is problematic.
BreakTagNotAllowed = ["ecommDescription100", "ecommDescription240", "productTitle",
"skuDifferentiatorText"]
app = xw.apps.active
wb = xw.Book(r'C:\Users\XXXX\Documents\Import File.xlsx')
#identifies the blanket range of interest
firstCell = xw.Range('A1')
lstcolumn = firstCell.end("right").column
headers_Row = xw.Range((1,1), (1, lstcolumn)).value
columnDict = {}
for column in range(1, len(headers_Row) + 1):
header = headers_Row[column - 1]
columnDict[header] = column
try:
articleColumn = columnDict["Product_Article_Number"]
except:
articleColumn = columnDict["Family_Article_Number"]
firstCell = xw.Range((1,articleColumn))
bttm = firstCell.end("down").row
wholeRange = xw.Range((1,1),(bttm, lstcolumn))
wholeRangeVal = wholeRange.value
#Sets the font and deletes previous conditional formatting
wholeRange.api.Font.Name = "Arial Unicode MS"
wholeRange.api.FormatConditions.Delete()
for columnHeader in columnDict.keys():
if columnHeader in CharLimits_Fields.keys():
rng, cellRef = setRange(columnDict, columnHeader)
rng.api.FormatConditions.Add(2,3, "=len(" + cellRef + ") >=" + str(CharLimits_Fields[columnHeader]))
rng.api.FormatConditions(1).Interior.ColorIndex = 3
if columnHeader in BreakTagNotAllowed:
rng, cellRef = setRange(columnDict, columnHeader)
rng.api.FormatConditions.Add(2,3, '=OR(ISNUMBER(SEARCH("<br>",' + cellRef + ')), ISNUMBER(SEARCH("<br/>",' + cellRef + ")))")
rng.api.FormatConditions(2).Interior.ColorIndex = 6
searchResults = wholeRange.api.Find("~\"")
if searchResults is not None:
msg_box("There's a double quote in this spreadsheet")
else:
msg_box("There are no double quotes in this spreadsheet")
# app.api.FindFormat.Clear
# app.api.FindFormat.Interior.ColorIndex = 3
# foundRed = wholeRange.api.Find("*", SearchFormat=True)
# if foundRed is None:
# msg_box("There are no values exceeding character limits")
# else:
# msg_box("There are values exceeding character limits")
# app.api.FindFormat.Clear
# app.api.FindFormat.Interior.ColorIndex = 6
# foundYellow = wholeRange.api.Find("*", SearchFormat=True)
# if foundYellow is None:
# msg_box("There are no break tags in this spreadsheet")
# else:
# msg_box("There are break tags in this spreadsheet")
Note:
If you are reading this, I would try Santiago's solution first.
The issue:
Looking at what is likely at line 22 on the github package:
async def concat_async(async_gen):
This is making use of the async keyword which was added in python 3.5, however py2exe only supports up to python 3.4. Now jinja looks to be extending the python language in some way (perhaps during runtime?) to support this async keyword in earlier versions of python. py2exe cannot account for this language extension.
The Fix:
async support was added in jinja2 version 2.9 according to the documentation. So I tried installing an earlier version of jinja (version 2.8) which I downloaded here.
I made a backup of my current jinja installation by moving the contents of %PYTHONHOME%\Lib\site-packages\jinja2 to some other place.
extract the previously downloaded tar.gz file and install the package via pip:
cd .\Downloads\dist\Jinja2-2.8 # or wherever you extracted jinja2.8
python setup.py install
As a side note, I also had to increase my recursion limit because py2exe was reaching the default limit.
from distutils.core import setup
import py2exe
import sys
sys.setrecursionlimit(5000)
setup (console=['test.py'])
Warning:
If whatever it is you are using relies on the latest version of jinja2, then this might fail or have unintended side effects when actually running your code. I was compiling a very simple script.
I had the same trouble coding in python3.7. I fixed that adding the excludes part to my py2exe file:
a = Analysis(['pyinst_test.py'],
#...
excludes=['jinja2.asyncsupport','jinja2.asyncfilters'],
#...)
I took that from: https://github.com/pyinstaller/pyinstaller/issues/2393

Progress bar in Sublime Text with Python

I am using the powerful Sublime Text 3 editor on MacOSX to run python codes. I want to display a progress bar of a for loop, and the following command:
sys.stdout.write('\rProgress : %03.2f %%' % (100*float(i)/(N)))
sys.flush()
does not clear the previously printed line in the output window as expected (\r) but produces N lines:
Progress : 0.25 %
Progress : 0.50 %
Progress : 0.75 %
Progress : 1.00 %
Progress : 1.25 %
Progress : 1.50 %
Progress : 1.75 %
Progress : 2.00 %
Progress : 2.25 %
Progress : 2.50 %
...
Which is not really nice to read – I conclude that the output window might be read-only.
Does anyone have suggestions to improve the use of progress bars in Sublime Text?
As another alternative solution you can use status bar. When you you set the status bar message the previous text is cleared. Package control also uses status bar while installing packages.
Example:
import sublime, sublime_plugin
import time
class ExampleCommand(sublime_plugin.WindowCommand):
def run(self, args):
sublime.set_timeout_async(self.test,1)
def test(self):
i=80
while i <= 100:
sublime.status_message('%03.2f %%' % i)
time.sleep(0.15)
i+=0.25
sublime.status_message('100% Stackoverflow!')
Taking a look at sublime.py we see that the flush method actually does nothing:
class _LogWriter:
def flush(self):
pass
def write(self, s):
sublime_api.log_message(s)
sys.stdout = _LogWriter()
sys.stderr = _LogWriter()
However I would not recommend to use the console for user outputs anyway. Usually you use output panels/views or status messages.
Status messages are easier to use, but less powerful. sergioFC demonstrated this in his answer.
This demonstrates how to use an output panel. It is very flexible, but you must write your own text command to insert the text. This is necessary, because you need an edit object to change the content of the view.
import sublime
import sublime_plugin
class MyInsertProgressBarCommand(sublime_plugin.TextCommand):
def run(self, edit, value):
view = self.view
width, _ = view.viewport_extent()
em_width = view.em_width()
# the number of columns are the width divided by the width of a char
# subtract two to add a little border
columns = int(width / em_width) - 2
# subtract two, because we surround it with [ and ]
bar_length = columns - 2
# calculate the size of the filled and the remaining part
filled_length = int(bar_length * value / 100)
remaining_length = bar_length - filled_length
# assemble the string for the progress bar
text = "[{0}{1}]\n".format("=" * filled_length, "." * remaining_length)
# add the text for the percentages
if value >= 100:
percentage_text = "finished!"
else:
percentage_text = "{:3.2f} %".format(value)
text += " " * (columns - len(percentage_text)) + percentage_text
# replace the content of the view
view.replace(edit, sublime.Region(0, view.size()), text)
# reset sels
view.sel().clear()
view.sel().add(sublime.Region(0, 0))
class ProgressBarCommand(sublime_plugin.WindowCommand):
def run(self):
self.window.create_output_panel("progess_bar")
self.window.run_command("show_panel", {"panel": "output.progess_bar"})
def test_progress_bar():
import random
test_progress_bar.value += 2 * random.random()
if test_progress_bar.value >= 100:
self.finish_progress()
return
self.show_progress(test_progress_bar.value)
sublime.set_timeout(test_progress_bar, 100)
test_progress_bar.value = 0
sublime.set_timeout_async(test_progress_bar, 1)
def show_progress(self, progess):
view = self.window.find_output_panel("progess_bar")
view.run_command("my_insert_progress_bar", {"value": progess})
def finish_progress(self):
self.show_progress(100)
sublime.set_timeout(self._destroy, 5000)
def _destroy(self):
self.window.destroy_output_panel("progess_bar")
The output:
You can create a visual progress bar using:
the mdpopups library
sublime.set_timeout or sublime.set_timeout_async ( see: Sublime Module )
 
Demo:
 
Code:
# GitHub
( run the plugin by typing Progress Bar Demo # the Command Palette )
 
 
Notes:
There is a css file that controls the style of mdpopups.
For some reason, the color property isn't having any effect.
Also, mdpopups.show_popup's location parameter takes -1 for the popup to be set at the caret position.
Otherwise, I'm not sure how location affects the popup, since it only takes a single integer value.  
 
I've inquired about these issues at the following thread:
[Proof Of Concept] Visual Progress Bar
 
Unfortunately, this is not possible in Sublime's output panel. The panel is not a true console or terminal, and among other differences does not interpret escape sequences such as \r and \b (\n is interpreted correctly, however). If you want to see how exactly it works, install PackageResourceViewer, then open Packages/Default/exec.py.
In order to get this to work, you'll need to create a new build system to run it in Terminal. Due to the vagaries of OS X, you'll need to create two files. The first is a shell script:
#!/bin/sh
osascript -e '
on run parameters
tell application "Terminal"
activate
do script with command "/path/to/python " & parameters
end tell
end run
' $#
Change /path/to with your actual path to python (or python3). Save it wherever you want as PythonTerminal.sh. Next, select Tools -> Build System -> New Build System and paste in the following:
{
"cmd": ["/bin/sh /path/to/Python3Terminal.sh \"$file\""],
"file_regex": "^[ ]*File \"(...*?)\", line ([0-9]*)",
"selector": "source.python",
"shell": true
}
Again, change /path/to to the actual path to PythonTerminal.sh. Save the file as Packages/User/PythonTerminal.sublime-build (it should automatically open the correct directory when saving).
Finally, select Tools -> Build System -> PythonTerminal, switch to your Python file, and build with ⌘B. A new Terminal window will open and your progress bar should run.
What you might be looking for is a way to keep the output from consuming multiple lines. You can print \b (the backspace character) as many times as there were previously printed characters. I wrote this as an example:
(Python 2.7.6)
from __future__ import print_function
import time, sys
for i in range(1, 6):
print(i, end='')
sys.stdout.flush()
time.sleep(0.5)
print('\b', end='')
Try running that and you can adapt it to your needs.
You can use progressbar library. located here : https://pypi.python.org/pypi/progressbar/2.3-dev
Also you can install it from easy_install just type : easy_install progressbar
Example to use :
if you want simple progressbar with out information about function :
from progressbar import *
from time import sleep
progress = ProgressBar()
for i in progress(range(80)):
sleep(0.01)
else if you want progressbar with information about function :
from progressbar import *
from time import sleep
widgets = ['Something: ', Percentage(), ' ', Bar(marker=RotatingMarker()),
' ', ETA(), ' ', FileTransferSpeed()]
pbar = ProgressBar(widgets=widgets, maxval=10000000).start()
for i in range(1000000):
# do something
pbar.update(10*i+1)
sleep(0.000001)
pbar.finish()

Categories