# No cancel button.

from pywin.mfc import dialog
from pywin.mfc.thread import WinThread
import threading
import win32ui
import win32con
import win32api
import time

def MakeProgressDlgTemplate(caption, staticText = ""):
    style = (win32con.DS_MODALFRAME |
	     win32con.WS_POPUP |
	     win32con.WS_VISIBLE |
	     win32con.WS_CAPTION |
	     win32con.WS_SYSMENU |
	     win32con.DS_SETFONT)
    cs = (win32con.WS_CHILD |
	  win32con.WS_VISIBLE)

    w = 215
    h = 36 # With button
    h = 40

    dlg = [[caption,
	    (0, 0, w, h),
	    style,
	    None,
	    (8, "MS Sans Serif")],
	   ]

    s = win32con.WS_TABSTOP | cs
    
    dlg.append([130, staticText, 1000, (7, 7, w-7, h-32), cs | win32con.SS_LEFT])

#    dlg.append([128,
#		"Cancel",
#		win32con.IDCANCEL,
#		(w - 60, h - 18, 50, 14), s | win32con.BS_PUSHBUTTON])

    return dlg

class CStatusProgressDialog(dialog.Dialog):
	def __init__(self, title, msg = "", maxticks = 100, tickincr = 1):
		self.initMsg = msg
		templ = MakeProgressDlgTemplate(title, msg)
		dialog.Dialog.__init__(self, templ)
		self.maxticks = maxticks
		self.tickincr = tickincr
		self.pbar = None
		
	def OnInitDialog(self):
		rc = dialog.Dialog.OnInitDialog(self)
		self.static = self.GetDlgItem(1000)
		self.pbar = win32ui.CreateProgressCtrl()
		self.pbar.CreateWindow (win32con.WS_CHILD |
						win32con.WS_VISIBLE,
						(10, 30, 310, 44),
						self, 1001)
		self.pbar.SetRange(0, self.maxticks)
		self.pbar.SetStep(self.tickincr)
		self.progress = 0
		self.pincr = 5
		return rc
	
	def Close(self):
		self.EndDialog(0)

	def SetMaxTicks(self, maxticks):
		if self.pbar is not None:
			self.pbar.SetRange(0, maxticks)

	def Tick(self):
		if self.pbar is not None:
			self.pbar.StepIt()

	def SetTitle(self, text):
		self.SetWindowText(text)

	def SetText(self, text):
		self.SetDlgItemText(1000, text)

	def Set(self, pos, max = None):
		if self.pbar is not None:
			self.pbar.SetPos(pos)
			if max is not None:
				self.pbar.SetRange(0, max)

# a progress dialog created in a new thread - especially suitable for
# console apps with no message loop.
MYWM_SETTITLE = win32con.WM_USER+10
MYWM_SETMSG = win32con.WM_USER+11
MYWM_TICK = win32con.WM_USER+12
MYWM_SETMAXTICKS = win32con.WM_USER+13
MYWM_SET = win32con.WM_USER+14

class CThreadedStatusProcessDialog(CStatusProgressDialog):
	def __init__(self, title, msg = "", maxticks = 100, tickincr = 1):
		self.title = title
		self.msg = msg
		self.threadid = win32api.GetCurrentThreadId()
		CStatusProgressDialog.__init__(self, title, msg, maxticks, tickincr)

	def OnInitDialog(self):
		rc = CStatusProgressDialog.OnInitDialog(self)
		self.HookMessage(self.OnTitle, MYWM_SETTITLE)
		self.HookMessage(self.OnMsg, MYWM_SETMSG)
		self.HookMessage(self.OnTick, MYWM_TICK)
		self.HookMessage(self.OnMaxTicks, MYWM_SETMAXTICKS)
		self.HookMessage(self.OnSet, MYWM_SET)
		return rc

	def _Send(self, msg):
		try:
			self.PostMessage(msg)
		except win32ui.error:
			# the user closed the window - but this does not cancel the
			# process - so just ignore it.
			pass

	def OnTitle(self, msg):
		CStatusProgressDialog.SetTitle(self, self.title)

	def OnMsg(self, msg):
		CStatusProgressDialog.SetText(self, self.msg)

	def OnTick(self, msg):
		CStatusProgressDialog.Tick(self)

	def OnMaxTicks(self, msg):
		CStatusProgressDialog.SetMaxTicks(self, self.maxticks)

	def OnSet(self, msg):
		CStatusProgressDialog.Set(self, self.pos, self.max)

	def Close(self):
		assert self.threadid, "No thread!"
		win32api.PostThreadMessage(self.threadid, win32con.WM_QUIT, 0, 0)

	def SetMaxTicks(self, maxticks):
		self.maxticks = maxticks
		self._Send(MYWM_SETMAXTICKS)
	def SetTitle(self, title):
		self.title = title
		self._Send(MYWM_SETTITLE)
	def SetText(self, text):
		self.msg = text
		self._Send(MYWM_SETMSG)
	def Tick(self):
		self._Send(MYWM_TICK)
	def Set(self, pos, max = None):
		self.pos = pos
		self.max = max
		self._Send(MYWM_SET)

class ProgressThread(WinThread):
	def __init__(self,  title, msg = "", maxticks = 100, tickincr = 1):
		self.title = title
		self.msg = msg
		self.maxticks = maxticks
		self.tickincr = tickincr
		self.dialog = None
		WinThread.__init__(self)
		self.createdEvent = threading.Event()

	def InitInstance(self):
		self.dialog = CThreadedStatusProcessDialog( self.title, self.msg, self.maxticks, self.tickincr)
		self.dialog.CreateWindow()
		try:
			self.dialog.SetForegroundWindow()
		except win32ui.error:
			pass
		self.createdEvent.set()
		return WinThread.InitInstance(self)

	def ExitInstance(self):
		return 0


def StatusProgressDialog(title, msg = "", maxticks = 100, parent = None):
	d = CStatusProgressDialog (title, msg, maxticks)
	d.CreateWindow (parent)
	return d

def ThreadedStatusProgressDialog(title, msg = "", maxticks = 100):
	t = ProgressThread(title, msg, maxticks)
	t.CreateThread()
	# Need to run a basic "PumpWaitingMessages" loop just incase we are
	# running inside Pythonwin.
	# Basic timeout incase things go terribly wrong.  Ideally we should use
	# win32event.MsgWaitForMultipleObjects(), but we use a threading module
	# event - so use a dumb strategy
	end_time = time.time() + 10
	while time.time() < end_time:
		if t.createdEvent.isSet():
			break
		win32ui.PumpWaitingMessages()
		time.sleep(0.1)
	return t.dialog

def demo():
	d = StatusProgressDialog("A Demo", "Doing something...")
	import win32api
	for i in range(100):
		if i == 50:
			d.SetText("Getting there...")
		if i==90:
			d.SetText("Nearly done...")
		win32api.Sleep(20)
		d.Tick()
	d.Close()

def thread_demo():
	d = ThreadedStatusProgressDialog("A threaded demo", "Doing something")
	import win32api
	for i in range(100):
		if i == 50:
			d.SetText("Getting there...")
		if i==90:
			d.SetText("Nearly done...")
		win32api.Sleep(20)
		d.Tick()
	d.Close()

if __name__=='__main__':
	thread_demo()
	#demo()
