From f6b4645013fbb782fa4c0f7318bdf8faf56da41c Mon Sep 17 00:00:00 2001 From: Chris Kiehl Date: Sat, 1 Feb 2014 23:13:04 -0500 Subject: [PATCH] moved image resizing funtion into own module. Changed header and footer to support 'pages' and updated their images --- src/app/dialogs/controller.py | 7 ++- src/app/dialogs/footer.py | 51 ++++++++++++++----- src/app/dialogs/footer.pyc | Bin 5261 -> 6158 bytes src/app/dialogs/header.py | 86 ++++++++++++++++++++------------- src/app/dialogs/header.pyc | Bin 4650 -> 4816 bytes src/app/dialogs/imageutil.py | 43 +++++++++-------- src/app/images/__init__.py | 2 +- src/app/images/__init__.pyc | Bin 2500 -> 2518 bytes src/app/images/image_store.py | 1 + src/app/images/image_store.pyc | Bin 1206 -> 1295 bytes src/app/images/loader.gif | Bin 0 -> 10819 bytes src/experiments/command.py | 51 ++++++++++++++----- 12 files changed, 158 insertions(+), 83 deletions(-) create mode 100644 src/app/images/loader.gif diff --git a/src/app/dialogs/controller.py b/src/app/dialogs/controller.py index 13108c9..488d4c7 100644 --- a/src/app/dialogs/controller.py +++ b/src/app/dialogs/controller.py @@ -62,13 +62,16 @@ class Controller(object): self._base.NextPage() self._payload_runner() - def OnCancelRunButton(self, event): - pass + def OnCloseButton(self, event): + self._base.Destroy() + sys.exit() def RunClientCode(self): pool = Pool(1) try: pool.apply(self._base._payload) + self._head.NextPage() + self._foot.NextPage() self.ShowGoodFinishedDialog() except: self.ShowBadFinishedDialog(traceback.format_exc()) diff --git a/src/app/dialogs/footer.py b/src/app/dialogs/footer.py index 5b863e6..eb13e21 100644 --- a/src/app/dialogs/footer.py +++ b/src/app/dialogs/footer.py @@ -5,6 +5,8 @@ Created on Dec 23, 2013 ''' import wx +import wx.animate +import imageutil from app.images import image_store class AbstractFooter(wx.Panel): @@ -18,8 +20,10 @@ class AbstractFooter(wx.Panel): self._controller = None self._init_components() + self._init_pages() self._do_layout() + def _init_components(self): ''' initialize all of the components used in the footer @@ -30,10 +34,24 @@ class AbstractFooter(wx.Panel): ''' self.cancel_button = self._Button('Cancel', wx.ID_CANCEL) self.start_button = self._Button("Start", wx.ID_OK) - self.cancel_run_button = self._Button('Cancel', wx.ID_CANCEL) - -# _bitmap = wx.Bitmap(image_store.alessandro_rei_checkmark) -# wx.StaticBitmap(self, -1, _bitmap) + self.running_animation = wx.animate.GIFAnimationCtrl(self, -1, image_store.loader) + self.close_button = self._Button("Close", wx.ID_OK) + + def _init_pages(self): + _pages = [[ + self.cancel_button.Hide, + self.start_button.Hide, + self.running_animation.Show, + self.running_animation.Play, + self.Layout + ], + [ + self.running_animation.Stop, + self.running_animation.Hide, + self.close_button.Show, + self.Layout + ]] + self._pages = iter(_pages) def _do_layout(self): v_sizer = wx.BoxSizer(wx.VERTICAL) @@ -45,8 +63,10 @@ class AbstractFooter(wx.Panel): v_sizer.AddStretchSpacer(1) v_sizer.Add(h_sizer, 0, wx.ALIGN_CENTER_VERTICAL | wx.EXPAND) - v_sizer.Add(self.cancel_run_button, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT | wx.RIGHT, 20) - self.cancel_run_button.Hide() + v_sizer.Add(self.running_animation, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT | wx.RIGHT, 20) + self.running_animation.Hide() + v_sizer.Add(self.close_button, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT | wx.RIGHT, 20) + self.close_button.Hide() v_sizer.AddStretchSpacer(1) self.SetSizer(v_sizer) @@ -63,10 +83,15 @@ class AbstractFooter(wx.Panel): self._controller = controller def NextPage(self): - self.cancel_button.Hide() - self.start_button.Hide() -# self.cancel_run_button.Show() - self.Layout() + page = next(self._pages) + for action in page: + action() + + def _load_image(self, img_path, height=70): + return imageutil._resize_bitmap( + self, + imageutil._load_image(img_path), + height) class Footer(AbstractFooter): @@ -84,14 +109,14 @@ class Footer(AbstractFooter): self.Bind(wx.EVT_BUTTON, self.OnCancelButton, self.cancel_button) self.Bind(wx.EVT_BUTTON, self.OnStartButton, self.start_button) - self.Bind(wx.EVT_BUTTON, self.OnCancelRunButton, self.cancel_run_button) + self.Bind(wx.EVT_BUTTON, self.OnCloseButton, self.close_button) def OnCancelButton(self, event): self._controller.OnCancelButton(event) event.Skip() - def OnCancelRunButton(self, event): - self._controller.OnCancelRunButton(event) + def OnCloseButton(self, event): + self._controller.OnCloseButton(event) event.Skip() def OnStartButton(self, event): diff --git a/src/app/dialogs/footer.pyc b/src/app/dialogs/footer.pyc index a1d73b71a352742752a2356471c4248b7c2e343c..106530c2344fd7eedb3ba576a7b62281531a432b 100644 GIT binary patch delta 1969 zcmah~O>7%g5T3W|U3>i(HziThKxq;oZKw)`fJBw5ZR*g(DCFn4DZ)}&w%)~A*Iozj zOOsTsERi1I04RDA7bLD6IOfD1&YZb$;oK8kI3SprbwUpy9KZ3rdGp?z@0*#I&*#3l zJZ1h?%CGUhXC(4vJlu1X4#rOwdNqmomibyt3#*P-8|hphaz9?hrSAfqpyA89sPOnN`_n; zHUxS8;w=~!o@KKgxAP4RTty?EE1*#TS$7M1RV;}$eI%;ZBY<{J|0L#wqyG?IxqugW zQ5-36$8UxDj`5jWp#X{~K#Yr$;TU<57fnHP92p!{97$VS=KNK~pxar%g98&(4%!YJ znl$zDB-?1u0J&x0&|rIk7^=JW@g36^qtRdZQ5GHB=_TQsThv}=mJr&_-(z)KfU8Q=lOBiH9ujOtJ za;Q$>NJxR)gqRTLQ=kUUIU-s`1VbQg!HNMx#UxOK5fzIS8(OJ?7UqPO(p|_*>Wn@a zXc;hgEI>K%NWdX`AYTC>FBIXLONo(ZQ7B@3B?>}0MyAOVQ+>O)$7>rM|A2{D^$&Uo zlKktcw=a+}l7 znjF(V*mE<4Qc`YG5R{;LsOr(fuFN>1? zD!VF*`q%7-1?KZIddb%avPI=m)8FO-QP4%_V6chvgs1~Ck`@8oLrhh1Bo|SrvsoO%5) zK}NlMk?Q(8iWu^!nY^g`68b$8D)Aq6TUb77o5S4>qt>>2x_yqQohumHYa;($@46YH6R`k9LObKqjj0 z@Ie?eGK;+Wdat}WM(oRF6#w|j6+V+z9dfenS64y!GAi;6d zm6C6xO)l^qi>Ug?Pb!~?>-+@VBq#1+Dgu7u*5h~mD6aZ(GwkrI@joyE7s0oF0=f9EJa2KDXN6M{a1!hy1b&F qo}DQWGD({mX&o!ydhu$v7wmSzYjkfyWojEmtf-=>7?WG$=l=%#{YV%9 delta 1154 zcmah{%TE(w5TDuI?yK}gO94R$4`D$?1c8H(sBl1S3T!ozCJME?C=ZLFTLVTzqC`(> z@Vglk&z?EUf8f!B7vsf?i80Z{#G5l;D|*n_?EE^fo%zkoJ{f%6*P?!l*fU>0x<#n{ zRQf+nQeNI}X`WVLMS+_t5 zY`<8?^P(fwG2p4}<+ly_Xt1R~o^)9`Z*o;mu)xhZrr5vF2YJTnqp9y6EBrV zJyE7=69A?$qF9QJMP!SFM;Rw6XGB&V7BgzvBw8xu9Bt>ds-v294DnW*ZcR{9u}~`d zD|2g=(%iL55UiFQF5%i@K7Fy*B(;$%=>%XlVNT9b3GK9FKDam$|$Ap4%^;CTCliCeK&q`|5Nn%d%otEM{8T&hzLy%rK20 zLNHaz1%yabQ+_NX#@0v-221EzIVKEW?p2GH2*tIinmYkkqadN5|mYgA9il4l_tX z+|rfZhIlWb0kS7|J}2*EoY(IjVUQFD#b!gpJ|6u)L#_%VeJ(!6iPhP-ge2e3&f6qq z4gfm*Y5$eh7?WuqrSJlYJgRQekqn2w=_-FkEH!rOOxPWz^vmK?V||oYuPb#JSDH_y G+kOIn!m}{| diff --git a/src/app/dialogs/header.py b/src/app/dialogs/header.py index b6df726..7e63e83 100644 --- a/src/app/dialogs/header.py +++ b/src/app/dialogs/header.py @@ -5,9 +5,11 @@ Created on Dec 23, 2013 ''' import wx +import itertools import imageutil from app.images import image_store + PAD_SIZE = 10 class FrameHeader(wx.Panel): @@ -25,6 +27,7 @@ class FrameHeader(wx.Panel): self._init_properties() self._init_components(heading, subheading, image_path) + self._init_pages() self._do_layout() @@ -35,8 +38,10 @@ class FrameHeader(wx.Panel): def _init_components(self, heading, subheading, image_path): self._header = self._bold_static_text(heading) self._subheader = wx.StaticText(self, label=subheading) - self._settings_img = self._load_image(image_path) - self._running_img = self._load_image(image_store.harwen_monitor) + self._settings_img = self._load_image(image_path, height=79) + self._running_img = self._load_image(image_store.computer3, 79) + self._check_mark = self._load_image(image_store.alessandro_rei_checkmark, height=75) + def _do_layout(self): vsizer = wx.BoxSizer(wx.VERTICAL) @@ -45,7 +50,9 @@ class FrameHeader(wx.Panel): sizer.Add(headings_sizer, 1, wx.ALIGN_LEFT | wx.ALIGN_CENTER_HORIZONTAL | wx.EXPAND | wx.LEFT, PAD_SIZE) sizer.Add(self._settings_img, 0, wx.ALIGN_RIGHT | wx.EXPAND | wx.RIGHT, PAD_SIZE) sizer.Add(self._running_img, 0, wx.ALIGN_RIGHT | wx.EXPAND | wx.RIGHT, PAD_SIZE) - self._running_img.Hide() + sizer.Add(self._check_mark, 0, wx.ALIGN_RIGHT | wx.EXPAND | wx.RIGHT, PAD_SIZE) + self._running_img.Hide() + self._check_mark.Hide() vsizer.Add(sizer, 1, wx.EXPAND) self.SetSizer(vsizer) @@ -56,6 +63,11 @@ class FrameHeader(wx.Panel): wx.FONTWEIGHT_NORMAL, wx.FONTWEIGHT_BOLD, False) ) return txt + + def _load_image(self, img_path, height=70): + return imageutil._resize_bitmap(self, + imageutil._load_image(img_path), + height) def build_heading_sizer(self): sizer = wx.BoxSizer(wx.VERTICAL) @@ -65,42 +77,48 @@ class FrameHeader(wx.Panel): sizer.AddStretchSpacer(1) return sizer - def _load_image(self, image_path): - try: - bitmap = wx.Bitmap(image_path) - print bitmap - bitmap = self._resize_bitmap(bitmap) - return wx.StaticBitmap(self, -1, bitmap) - except: - raise IOError('Invalid Image path') - - def _resize_bitmap(self, bmap): - ''' - Resizes a bitmap to a height of 89 pixels (the - size of the top panel), while keeping aspect ratio - in tact - ''' - image = wx.ImageFromBitmap(bmap) - width, height = image.GetSize() - ratio = float(width) / height - target_height = 79 - image = image.Scale(target_height * ratio, target_height, - wx.IMAGE_QUALITY_HIGH - ) - return wx.BitmapFromImage(image) - def RegisterController(self, controller): if self._controller is None: self._controller = controller + def _init_pages(self): + messages = [[ + "Running", + 'Please wait while the application performs its tasks. ' + + '\nThis may take a few moments' + ],[ + 'Finished', + 'All done! You may now safely close the program.' + ]] + pages = [[ + self._header.SetLabel, + self._subheader.SetLabel, + self._settings_img.Hide, + self._running_img.Show, + self.Layout, + ],[ + self._header.SetLabel, + self._subheader.SetLabel, + self._running_img.Hide, + self._check_mark.Show, + self.Layout, + ]] + self._messages = iter(messages) + self._pages = iter(pages) + def NextPage(self): - self._header.SetLabel("Running") - self._subheader.SetLabel('Please wait while the application performs its tasks. ' - '\nThis may take a few moments') - self._settings_img.Hide() - self._running_img.Show() - self.Layout() - + messages = next(self._messages) + commands = next(self._pages) + + _zipl = itertools.izip_longest + + for func, arg in _zipl(commands, messages, fillvalue=None): + if arg: + func(arg) + else: + func() + + diff --git a/src/app/dialogs/header.pyc b/src/app/dialogs/header.pyc index e3a11092d033a0a8380e029ccf84ac940d1be64f..d9521f9850d1d52c13c6f21f7b4d428cf426f144 100644 GIT binary patch delta 1894 zcmZ8iOOG2x5U!r_j6IIM-d7&Zl6bOt$N{nu3JQu4A`vJA5td~(0%9c$6VI$Y-WiYM zX_MWRS9?eVQX~+YzHo{-M%>{KaO1=e;KC1pO9U4r#8J@1X zuWfojbf4&EtF+tOn!SrXBBB=Fkp5P8|9SnjutX08wMrB^yu9U5Sk_9J!UI}4Kw(8I z74lY_Q|dF(5Fe`zarMxcs8`|>1a@3)3LLQ9bkwgRnmPd8kNdIo2SF!N=Zv4{XE=rm z1Qq8D$0&a%QNCf7@M2N@VICKoj|vxeLWASGF9Z(pZQ`RWl0`m{=MA3m zo#*CpU$_+ZU2to@lZ9QECCenZ6D87y){Wv$SH>SfjX!{B@}(qS&%cu;p`Xh@s&V1- zz`;~IO9vwv4KGMuwe%$Z)aoUkbFcBnCo(KJKnEAsf_S@a4y`Bccb*TZ+rq6b> zfW;nf;Z<2ov8Zw2uka~nF!C}4iCJ+(KW&DSz4-F)VC2Vy`p)`EE!d8zsedhZ9+*r~ zZ(3)70jj}J*X@RwQHgy_%&Ld>W3iyl7LVJa3`U zCGoO;s_MVuxQ_j)b3uQezTM>8dCW!ch*G7mY#qzIYB;?e-r`M+NdyP6S6v)3;;Wcz zvkMOnMThYc6p7mkP97`Br9j$fY|wr48IRaUGB$Av290E#NCpis=kZqL--%@!47{V5 zsyBJ923InRkQ=a0cD;j)?;J1D2XpR8IEYjI;CyYnu)GuMT)BSq6sw0>)FGM%YdXI3 zyhHsY>}wc%>_v1{aCZ(;U71<|o{y$BcUJg4$4H_kbnf_4e55}6F%&LY&FaG)%~TXC z6nwWe6uo^+0LUIVJ(GHc3B`MRHBh9ErDtAP{0VGpe|3qTU(E*?4CMr6U|7~B;MNEG zlUm`+E~CT{{2t5mi|S$R(oW79E8pnGxtj)?t_;>AH*nXYdv2PgQD5d7^{c4%yc>o2 zd91#YByO1XqZ97UY^1&V**!N8)}mz7Z6{eCxw0F%gJISg2I-QnK%D@+)oLWKbG7Ph zc<}b82ehyn53GKdse3FFhFZANKI>GcVcS5ITt#w+a_(i zMPrkBkJKl=&}(s$ zd>JI8sL40dNcE!)>AeTbV+QXP6l0WSNv>O3ycG}pBIoLN>0@&RV@4#oI3tx1~c=n_aVD->yhjq e3*)}RD`-+%0ZR3AO4pBk? delta 1767 zcmZWpQEwYX5T3m|pMCb(aqOgNnzT8fk*+C<1Vlg(5)GwEjM9YMrI1FIE|%lvoOXVmA_ z@=rhh?gVi0QStv4j%@qM%>051Jr(*I>}kM4{3}3MfQUg?heHOT4qpL`0PgPtm%TlR8Sdoeej%!y%o6WH`@tCce7{#?2 z2Hmtv;x`tFUFG}67+tQ60*Xz=P|B<+EcF{!5#JVe3MDkp2~)eq?unb)+e;$`rvRQ# zvReRNgl-8gjCd1@ui%zVUO?IN52Bu37C&plaAetH}YynZr>d6IGyOsiV!SxV z>f(*!RW>fRici_J_^WvBl^OKpv~4{OBTfO`h`aHlD7`pbyjeQELSWp$ku^}zi4A75 zMYg~e#Iw@rbQ6v3x8&#f2R1o7M6z>9_{P_|ynj>tX>_M1(7J>pN0&`voN0DR5ua8b zFVE7Bt0;IK#Yz~uJdJqJaff~orH)0*lO(24NKA8NZc19_(D=Sc%;|GFnI0rD*g@sw zJ>Uoe^2CUkGX+i+SX(E)q?+5vMI{nLbW~uZg8SK^35+p>Cqx5-FBuFN?1scCow6DK zjUS0gYw*zT#i7;exBYfx4Sn9Ri3o`U*HQa0=6!$2iFGfHGDO%r$W278%Y7aP7d}WC zw6^Y~X_Crw@^{is87bc;o{n#yUq;(UI5JuUrm`_sz%hkdKv_qTUShG2lm7#O7>ocO zC~&62nFbs&P-XsVFnNZ>h=5^kE6~;86qN!@;2(u4zzDHMG-)DT5TZQUvm-pJY#5Gw zXJ+|UerJm(sCS~c-QiYpXuZ2^4dXAOUS`?66Ir-}aG@2N@??lW4x-+T*RA7D+>5Nw zqiBewVfooG3b>V?BWMY2iw73>15Vl&9&OXRbLgp-TYcnYHBI{e!FMgsa#@v!y~O8I za|6E@aZ2*m-IcXF-uAtfjn>Y6Z@sm)&M8zLfnrh6ttdZMGbrh7~_`_By^Pc4iPji{ga;_4u$iJKvh{qR`qSXG)b!=>iY2KSMN4#2F z7W=i?RhwFdtjib8V}p8}EK+Bh)@%|l+l<>ujwKo!{z-DgW$$&Uv$|q-yxQDH=Zof0 zXLY5@s!ZG(f9JaFDH9Zl3f-Z+cL*rwV-k{cpg!Oe3WRHR1%<-T$Gl~!5KL7Vxz%pw4y5}Ho>~0EB^pl Cc}nR3 diff --git a/src/app/dialogs/imageutil.py b/src/app/dialogs/imageutil.py index 76da8c1..933453f 100644 --- a/src/app/dialogs/imageutil.py +++ b/src/app/dialogs/imageutil.py @@ -8,20 +8,30 @@ from PIL import Image # @UnresolvedImport from app.images import image_store - -def LoadAndResizeImage(path): - im = Image.open(path) - return PilImageToWxImage(_Resize(im)) - -def _Resize(pil_image): +def _load_image(image_path): + try: + return wx.Bitmap(image_path) + except: + raise IOError('Invalid Image path') + +def _resize_bitmap(parent, _bitmap, target_height): ''' - Resizes a bitmap to a height of 79 pixels (the - size of the top panel -1), while keeping aspect ratio + Resizes a bitmap to a height of 89 pixels (the + size of the top panel), while keeping aspect ratio in tact ''' - target_size = _GetTargetSize(pil_image.size) - return pil_image.resize(target_size) - + image = wx.ImageFromBitmap(_bitmap) + _width, _height = image.GetSize() + if _height < target_height: + print 'returning image without resizing' + return wx.StaticBitmap(parent, -1, wx.BitmapFromImage(image)) + print 'returning resized image' + ratio = float(_width) / _height + image = image.Scale(target_height * ratio, target_height, + wx.IMAGE_QUALITY_HIGH + ) + return wx.StaticBitmap(parent, -1, wx.BitmapFromImage(image)) + def _GetTargetSize(size): width, height = size aspect_ratio = float(width)/height @@ -29,17 +39,8 @@ def _GetTargetSize(size): tWidth = int(tHeight * aspect_ratio) return (tWidth, tHeight) -def PilImageToWxImage(p_image): - wx_image = wx.EmptyImage(*p_image.size) - wx_image.SetData(p_image.convert( 'RGB' ).tostring()) - return wx_image.ConvertToBitmap() - if __name__ == '__main__': - app = wx.App() - print 'adsfasdf',LoadAndResizeImage(image_store.computer) - print 'asdfadf' - app.MainLoop() - + pass diff --git a/src/app/images/__init__.py b/src/app/images/__init__.py index 20d4cea..f3c56f1 100644 --- a/src/app/images/__init__.py +++ b/src/app/images/__init__.py @@ -31,7 +31,7 @@ def load_imagepaths(): file_extension = lambda x: os.path.splitext(x)[-1] return [os.path.join(PATH, f) for f in os.listdir(PATH) - if file_extension(f) in ('.jpeg','.png', '.ico')] + if file_extension(f) in ('.jpeg','.png', '.ico', '.gif')] diff --git a/src/app/images/__init__.pyc b/src/app/images/__init__.pyc index 7000bf3afa3058badb35425a5bb103343655edbf..64ec210742479a2e3676c69682e256c486770c59 100644 GIT binary patch delta 65 zcmX>id`+00`7$AvjZe$5%1ppp$ B3vU1b diff --git a/src/app/images/image_store.py b/src/app/images/image_store.py index 6b227a2..9059d63 100644 --- a/src/app/images/image_store.py +++ b/src/app/images/image_store.py @@ -13,6 +13,7 @@ computer2 = r"C:\Users\Chris\Dropbox\pretty_gui\Gooey\src\app\images\computer2.p computer3 = r"C:\Users\Chris\Dropbox\pretty_gui\Gooey\src\app\images\computer3.png" harwen_monitor = r"C:\Users\Chris\Dropbox\pretty_gui\Gooey\src\app\images\harwen_monitor.png" icon = r"C:\Users\Chris\Dropbox\pretty_gui\Gooey\src\app\images\icon.ico" +loader = r"C:\Users\Chris\Dropbox\pretty_gui\Gooey\src\app\images\loader.gif" settings = r"C:\Users\Chris\Dropbox\pretty_gui\Gooey\src\app\images\settings.png" settings2 = r"C:\Users\Chris\Dropbox\pretty_gui\Gooey\src\app\images\settings2.png" terminal = r"C:\Users\Chris\Dropbox\pretty_gui\Gooey\src\app\images\terminal.png" diff --git a/src/app/images/image_store.pyc b/src/app/images/image_store.pyc index d0fce9d773f49db6ba6e160471944346534027e8..081ca108c68d5b4c0614963b8d56da1c6b700f58 100644 GIT binary patch delta 104 zcmdnS+0P}-{F#@l;ON_+WCkc;1kw&bTe!gr7*Qbrs=pDj diff --git a/src/app/images/loader.gif b/src/app/images/loader.gif new file mode 100644 index 0000000000000000000000000000000000000000..d84f653789e5008da64ff04ee109471284a9e284 GIT binary patch literal 10819 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