1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433
|
# (C) Copyright 2005-2023 Enthought, Inc., Austin, TX
# All rights reserved.
#
# This software is provided without warranty under the terms of the BSD
# license included in LICENSE.txt and may be redistributed only under
# the conditions described in the aforementioned license. The license
# is also available online at http://www.enthought.com/licenses/BSD.txt
#
# Thanks for using Enthought open source!
""" A simple progress bar intended to run in the UI thread
"""
import warnings
import wx
import time
from traits.api import Bool, Instance, Int, Property, Str
from pyface.i_progress_dialog import MProgressDialog
from pyface.ui_traits import Orientation
from .widget import Widget
from .window import Window
class ProgressBar(Widget):
""" A simple progress bar dialog intended to run in the UI thread """
#: The progress bar's parent control.
parent = Instance(wx.Window)
#: The progress bar's control.
control = Instance(wx.Gauge)
#: The orientation of the progress bar.
direction = Orientation("horizontal")
#: The maximum value for the progress bar.
_max = Int()
def __init__(
self,
parent,
minimum=0,
maximum=100,
direction="horizontal",
size=(200, -1),
**traits,
):
"""
Constructs a progress bar which can be put into a panel, or optionaly,
its own window
"""
create = traits.pop("create", None)
# XXX minimum is ignored - it either should be deprecated or supported
super().__init__(
parent=parent,
_max=maximum,
direction=direction,
size=size,
**traits,
)
if create:
self.create()
warnings.warn(
"automatic widget creation is deprecated and will be removed "
"in a future Pyface version, code should not pass the create "
"parameter and should instead call create() explicitly",
DeprecationWarning,
stacklevel=2,
)
elif create is not None:
warnings.warn(
"setting create=False is no longer required",
DeprecationWarning,
stacklevel=2,
)
def _create_control(self, parent):
style = wx.GA_HORIZONTAL
if self.direction == "vertical":
style = wx.GA_VERTICAL
return wx.Gauge(parent, -1, self._max, style=style, size=self.size)
def update(self, value):
""" Update the progress bar to the desired value. """
if self._max == 0:
self.control.Pulse()
else:
self.control.SetValue(value)
self.control.Update()
def _show(self):
# Show the parent
self.parent.Show()
# Show the toolkit-specific control in the parent
self.control.Show()
class ProgressDialog(MProgressDialog, Window):
""" A simple progress dialog window which allows itself to be updated """
#: The progress bar
progress_bar = Instance(ProgressBar)
#: The window title
title = Str()
#: The text message to display in the dialog
message = Property()
#: The minimum value of the progress range
min = Int()
#: The minimum value of the progress range
max = Int()
#: The margin around the progress bar
margin = Int(5)
#: Whether or not the progress dialog can be cancelled
can_cancel = Bool(False)
#: Whether or not to show the time taken
show_time = Bool(False)
#: Whether or not to show the percent completed
show_percent = Bool(False)
#: Whether or not the dialog was cancelled by the user
_user_cancelled = Bool(False)
#: The text of the message label
_message_text = Str()
#: The size of the dialog
dialog_size = Instance(wx.Size)
# Label for the 'cancel' button
cancel_button_label = Str("Cancel")
#: The widget showing the message text
_message_control = Instance(wx.StaticText)
#: The widget showing the time elapsed
_elapsed_control = Instance(wx.StaticText)
#: The widget showing the estimated time to completion
_estimated_control = Instance(wx.StaticText)
#: The widget showing the estimated time remaining
_remaining_control = Instance(wx.StaticText)
def __init__(self, *args, **kw):
if "message" in kw:
self._message_text = kw.pop("message")
# initialize the start time in case some tries updating
# before open() is called
self._start_time = 0
super().__init__(*args, **kw)
# -------------------------------------------------------------------------
# IWindow Interface
# -------------------------------------------------------------------------
def open(self):
""" Opens the window. """
super().open()
self._start_time = time.time()
wx.GetApp().Yield(True)
def close(self):
""" Closes the window. """
if self.progress_bar is not None:
self.progress_bar.destroy()
self.progress_bar = None
if self._message_control is not None:
self._message_control = None
super().close()
# -------------------------------------------------------------------------
# IProgressDialog Interface
# -------------------------------------------------------------------------
def change_message(self, value):
""" Change the displayed message in the progress dialog
Parameters
----------
message : str or unicode
The new message to display.
"""
self._message_text = value
if self._message_control is not None:
self._message_control.SetLabel(value)
self._message_control.Update()
msg_control_size = self._message_control.GetSize()
self.dialog_size.x = max(
self.dialog_size.x, msg_control_size.x + 2 * self.margin
)
if self.control is not None:
self.control.SetClientSize(self.dialog_size)
self.control.GetSizer().Layout()
def update(self, value):
""" Update the progress bar to the desired value
If the value is >= the maximum and the progress bar is not contained
in another panel the parent window will be closed.
Parameters
----------
value :
The progress value to set.
"""
if self.progress_bar is None:
# the developer is trying to update a progress bar which is already
# done. Allow it, but do nothing
return (False, False)
self.progress_bar.update(value)
# A bit hackish, but on Windows if another window sets focus, the
# other window will come to the top, obscuring the progress dialog.
# Only do this if the control is a top level window, so windows which
# embed a progress dialog won't keep popping to the top
# When we do embed the dialog, self.control may be None since the
# embedding might just be grabbing the guts of the control. This happens
# in the Traits UI ProgressEditor.
if self.control is not None and self.control.IsTopLevel():
self.control.Raise()
if self.max > 0:
percent = (float(value) - self.min) / (self.max - self.min)
if self.show_time and (percent != 0):
current_time = time.time()
elapsed = current_time - self._start_time
estimated = elapsed / percent
remaining = estimated - elapsed
self._set_time_label(elapsed, self._elapsed_control)
self._set_time_label(estimated, self._estimated_control)
self._set_time_label(remaining, self._remaining_control)
if self.show_percent:
self._percent_control = "%3f" % ((percent * 100) % 1)
if value >= self.max or self._user_cancelled:
self.close()
else:
if self._user_cancelled:
self.close()
wx.GetApp().Yield(True)
return (not self._user_cancelled, False)
# -------------------------------------------------------------------------
# Private Interface
# -------------------------------------------------------------------------
def _on_cancel(self, event):
self._user_cancelled = True
self.close()
def _on_close(self, event):
self._user_cancelled = True
return self.close()
def _set_time_label(self, value, control):
hours = value // 3600
minutes = (value % 3600) // 60
seconds = value % 60
label = "%u:%02u:%02u" % (hours, minutes, seconds)
control.SetLabel(label)
def _get_message(self):
return self._message_text
def _create_buttons(self, dialog, parent_sizer):
""" Creates the buttons. """
sizer = wx.BoxSizer(wx.HORIZONTAL)
self._cancel = None
if self.can_cancel:
# 'Cancel' button.
self._cancel = cancel = wx.Button(
dialog, wx.ID_CANCEL, self.cancel_button_label
)
dialog.Bind(wx.EVT_BUTTON, self._on_cancel, id=wx.ID_CANCEL)
sizer.Add(cancel, 0, wx.LEFT, 10)
button_size = cancel.GetSize()
self.dialog_size.x = max(
self.dialog_size.x, button_size.x + 2 * self.margin
)
self.dialog_size.y += button_size.y + 2 * self.margin
parent_sizer.Add(sizer, 0, wx.ALIGN_RIGHT | wx.ALL, self.margin)
def _create_label(self, dialog, parent_sizer, text):
local_sizer = wx.BoxSizer()
dummy = wx.StaticText(dialog, -1, text)
label = wx.StaticText(dialog, -1, "unknown")
local_sizer.Add(dummy, 1, wx.ALIGN_LEFT)
local_sizer.Add(label, 1, wx.ALIGN_LEFT | wx.ALIGN_RIGHT, self.margin)
parent_sizer.Add(
local_sizer, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.TOP, self.margin
)
return label
def _create_gauge(self, dialog, parent_sizer):
self.progress_bar = ProgressBar(dialog, self.min, self.max)
self.progress_bar.create()
parent_sizer.Add(
self.progress_bar.control, 0, wx.CENTER | wx.ALL, self.margin
)
horiz_spacer = 50
progress_bar_size = self.progress_bar.control.GetSize()
self.dialog_size.x = max(
self.dialog_size.x,
progress_bar_size.x + 2 * self.margin + horiz_spacer,
)
self.dialog_size.y += progress_bar_size.y + 2 * self.margin
def _create_message(self, dialog, parent_sizer):
self._message_control = wx.StaticText(dialog, -1, self.message)
parent_sizer.Add(
self._message_control, 0, wx.LEFT | wx.TOP, self.margin
)
msg_control_size = self._message_control.GetSize()
self.dialog_size.x = max(
self.dialog_size.x, msg_control_size.x + 2 * self.margin
)
self.dialog_size.y += msg_control_size.y + 2 * self.margin
def _create_percent(self, dialog, parent_sizer):
if not self.show_percent:
return
raise NotImplementedError()
def _create_timer(self, dialog, parent_sizer):
if not self.show_time:
return
self._elapsed_control = self._create_label(
dialog, parent_sizer, "Elapsed time : "
)
self._estimated_control = self._create_label(
dialog, parent_sizer, "Estimated time : "
)
self._remaining_control = self._create_label(
dialog, parent_sizer, "Remaining time : "
)
elapsed_size = self._elapsed_control.GetSize()
estimated_size = self._estimated_control.GetSize()
remaining_size = self._remaining_control.GetSize()
timer_size = wx.Size()
timer_size.x = max(elapsed_size.x, estimated_size.x, remaining_size.x)
timer_size.y = elapsed_size.y + estimated_size.y + remaining_size.y
self.dialog_size.x = max(
self.dialog_size.x, timer_size.x + 2 * self.margin
)
self.dialog_size.y += timer_size.y + 2 * self.margin
def _create_control(self, parent):
""" Creates the window contents.
This method is intended to be overridden if necessary. By default we
just create an empty panel.
"""
style = (
wx.DEFAULT_FRAME_STYLE | wx.FRAME_NO_WINDOW_MENU | wx.CLIP_CHILDREN
)
dialog = wx.Frame(
parent,
-1,
self.title,
style=style,
size=self.size,
pos=self.position,
)
sizer = wx.BoxSizer(wx.VERTICAL)
dialog.SetSizer(sizer)
dialog.SetAutoLayout(True)
self.dialog_size = wx.Size()
# The 'guts' of the dialog.
self._create_message(dialog, sizer)
self._create_gauge(dialog, sizer)
self._create_percent(dialog, sizer)
self._create_timer(dialog, sizer)
self._create_buttons(dialog, sizer)
dialog.SetClientSize(self.dialog_size)
dialog.CentreOnParent()
return dialog
|