def __init__(self, parent): WizardPage.__init__(self, parent, pgid.FILES) # *** Left Panel *** # pnl_treeopts = BorderedPanel(self) self.chk_individuals = CheckBoxCFG(pnl_treeopts, label=GT(u'List files individually'), name=u'individually', cfgSect=u'FILES') self.chk_preserve_top = CheckBoxCFG(pnl_treeopts, chkid.TOPLEVEL, GT(u'Preserve top-level directories'), name=u'top-level', cfgSect=u'FILES') self.chk_nofollow_symlink = CheckBoxCFG(pnl_treeopts, chkid.SYMLINK, GT(u'Don\'t follow symbolic links'), defaultValue=True, name=u'nofollow-symlink', cfgSect=u'FILES') self.tree_dirs = DirectoryTreePanel(self, size=(300,20)) # ----- Target path pnl_target = BorderedPanel(self) # choices of destination rb_bin = wx.RadioButton(pnl_target, label=u'/bin', style=wx.RB_GROUP) rb_usrbin = wx.RadioButton(pnl_target, label=u'/usr/bin') rb_usrlib = wx.RadioButton(pnl_target, label=u'/usr/lib') rb_locbin = wx.RadioButton(pnl_target, label=u'/usr/local/bin') rb_loclib = wx.RadioButton(pnl_target, label=u'/usr/local/lib') self.rb_custom = wx.RadioButton(pnl_target, inputid.CUSTOM, GT(u'Custom')) self.rb_custom.Default = True # Start with "Custom" selected self.rb_custom.SetValue(self.rb_custom.Default) # group buttons together # FIXME: Unnecessary??? self.grp_targets = ( rb_bin, rb_usrbin, rb_usrlib, rb_locbin, rb_loclib, self.rb_custom, ) # ----- Add/Remove/Clear buttons btn_add = CreateButton(self, btnid.ADD) btn_remove = CreateButton(self, btnid.REMOVE) btn_clear = CreateButton(self, btnid.CLEAR) self.prev_dest_value = u'/usr/bin' self.ti_target = TextArea(self, defaultValue=self.prev_dest_value, name=u'target') self.btn_browse = CreateButton(self, btnid.BROWSE) btn_refresh = CreateButton(self, btnid.REFRESH) # Display area for files added to list self.lst_files = FileListESS(self, inputid.LIST, name=u'filelist') # *** Event Handling *** # # create an event to enable/disable custom widget for item in self.grp_targets: wx.EVT_RADIOBUTTON(item, wx.ID_ANY, self.OnSetDestination) # Context menu events for directory tree wx.EVT_MENU(self, wx.ID_ADD, self.OnImportFromTree) # Button events btn_add.Bind(wx.EVT_BUTTON, self.OnImportFromTree) btn_remove.Bind(wx.EVT_BUTTON, self.OnRemoveSelected) btn_clear.Bind(wx.EVT_BUTTON, self.OnClearFileList) self.btn_browse.Bind(wx.EVT_BUTTON, self.OnBrowse) btn_refresh.Bind(wx.EVT_BUTTON, self.OnRefreshFileList) # ???: Not sure what these do wx.EVT_KEY_DOWN(self.ti_target, self.GetDestValue) wx.EVT_KEY_UP(self.ti_target, self.CheckDest) # Key events for file list wx.EVT_KEY_DOWN(self.lst_files, self.OnRemoveSelected) self.Bind(wx.EVT_DROP_FILES, self.OnDropFiles) # *** Layout *** # lyt_treeopts = BoxSizer(wx.VERTICAL) lyt_treeopts.AddSpacer(5) lyt_treeopts.Add(self.chk_individuals, 0, lyt.PAD_LR, 5) lyt_treeopts.Add(self.chk_preserve_top, 0, lyt.PAD_LR, 5) lyt_treeopts.Add(self.chk_nofollow_symlink, 0, lyt.PAD_LR, 5) lyt_treeopts.AddSpacer(5) pnl_treeopts.SetSizer(lyt_treeopts) lyt_left = BoxSizer(wx.VERTICAL) lyt_left.AddSpacer(10) lyt_left.Add(wx.StaticText(self, label=GT(u'Directory options')), 0, wx.ALIGN_BOTTOM) lyt_left.Add(pnl_treeopts, 0, wx.EXPAND|wx.ALIGN_LEFT|wx.BOTTOM, 5) lyt_left.Add(self.tree_dirs, 1, wx.EXPAND) lyt_target = wx.GridSizer(3, 2, 5, 5) for item in self.grp_targets: lyt_target.Add(item, 0, lyt.PAD_LR, 5) pnl_target.SetAutoLayout(True) pnl_target.SetSizer(lyt_target) pnl_target.Layout() # Put text input in its own sizer to force expand lyt_input = BoxSizer(wx.HORIZONTAL) lyt_input.Add(self.ti_target, 1, wx.ALIGN_CENTER_VERTICAL) lyt_buttons = BoxSizer(wx.HORIZONTAL) lyt_buttons.Add(btn_add, 0) lyt_buttons.Add(btn_remove, 0) lyt_buttons.Add(btn_clear, 0) lyt_buttons.Add(lyt_input, 1, wx.ALIGN_CENTER_VERTICAL) lyt_buttons.Add(self.btn_browse, 0) lyt_buttons.Add(btn_refresh, 0) lyt_right = BoxSizer(wx.VERTICAL) lyt_right.AddSpacer(10) lyt_right.Add(wx.StaticText(self, label=GT(u'Target'))) lyt_right.Add(pnl_target, 0, wx.TOP, 5) lyt_right.Add(lyt_buttons, 0, wx.EXPAND) lyt_right.Add(self.lst_files, 5, wx.EXPAND|wx.TOP, 5) PROP_LEFT = 0 PROP_RIGHT = 1 lyt_main = wx.FlexGridSizer(1, 2) lyt_main.AddGrowableRow(0) # Directory tree size issues with wx 2.8 if wx.MAJOR_VERSION <= 2: PROP_LEFT = 1 lyt_main.AddGrowableCol(0, 1) lyt_main.AddGrowableCol(1, 2) lyt_main.Add(lyt_left, PROP_LEFT, wx.EXPAND|lyt.PAD_LR|wx.BOTTOM, 5) lyt_main.Add(lyt_right, PROP_RIGHT, wx.EXPAND|lyt.PAD_RB, 5) self.SetAutoLayout(True) self.SetSizer(lyt_main) self.Layout() SetPageToolTips(self)
class Page(WizardPage): ## Constructor # # \param parent # Parent <b><i>wx.Window</i></b> instance def __init__(self, parent): WizardPage.__init__(self, parent, pgid.FILES) # *** Left Panel *** # pnl_treeopts = BorderedPanel(self) self.chk_individuals = CheckBoxCFG(pnl_treeopts, label=GT(u'List files individually'), name=u'individually', cfgSect=u'FILES') self.chk_preserve_top = CheckBoxCFG(pnl_treeopts, chkid.TOPLEVEL, GT(u'Preserve top-level directories'), name=u'top-level', cfgSect=u'FILES') self.chk_nofollow_symlink = CheckBoxCFG(pnl_treeopts, chkid.SYMLINK, GT(u'Don\'t follow symbolic links'), defaultValue=True, name=u'nofollow-symlink', cfgSect=u'FILES') self.tree_dirs = DirectoryTreePanel(self, size=(300,20)) # ----- Target path pnl_target = BorderedPanel(self) # choices of destination rb_bin = wx.RadioButton(pnl_target, label=u'/bin', style=wx.RB_GROUP) rb_usrbin = wx.RadioButton(pnl_target, label=u'/usr/bin') rb_usrlib = wx.RadioButton(pnl_target, label=u'/usr/lib') rb_locbin = wx.RadioButton(pnl_target, label=u'/usr/local/bin') rb_loclib = wx.RadioButton(pnl_target, label=u'/usr/local/lib') self.rb_custom = wx.RadioButton(pnl_target, inputid.CUSTOM, GT(u'Custom')) self.rb_custom.Default = True # Start with "Custom" selected self.rb_custom.SetValue(self.rb_custom.Default) # group buttons together # FIXME: Unnecessary??? self.grp_targets = ( rb_bin, rb_usrbin, rb_usrlib, rb_locbin, rb_loclib, self.rb_custom, ) # ----- Add/Remove/Clear buttons btn_add = CreateButton(self, btnid.ADD) btn_remove = CreateButton(self, btnid.REMOVE) btn_clear = CreateButton(self, btnid.CLEAR) self.prev_dest_value = u'/usr/bin' self.ti_target = TextArea(self, defaultValue=self.prev_dest_value, name=u'target') self.btn_browse = CreateButton(self, btnid.BROWSE) btn_refresh = CreateButton(self, btnid.REFRESH) # Display area for files added to list self.lst_files = FileListESS(self, inputid.LIST, name=u'filelist') # *** Event Handling *** # # create an event to enable/disable custom widget for item in self.grp_targets: wx.EVT_RADIOBUTTON(item, wx.ID_ANY, self.OnSetDestination) # Context menu events for directory tree wx.EVT_MENU(self, wx.ID_ADD, self.OnImportFromTree) # Button events btn_add.Bind(wx.EVT_BUTTON, self.OnImportFromTree) btn_remove.Bind(wx.EVT_BUTTON, self.OnRemoveSelected) btn_clear.Bind(wx.EVT_BUTTON, self.OnClearFileList) self.btn_browse.Bind(wx.EVT_BUTTON, self.OnBrowse) btn_refresh.Bind(wx.EVT_BUTTON, self.OnRefreshFileList) # ???: Not sure what these do wx.EVT_KEY_DOWN(self.ti_target, self.GetDestValue) wx.EVT_KEY_UP(self.ti_target, self.CheckDest) # Key events for file list wx.EVT_KEY_DOWN(self.lst_files, self.OnRemoveSelected) self.Bind(wx.EVT_DROP_FILES, self.OnDropFiles) # *** Layout *** # lyt_treeopts = BoxSizer(wx.VERTICAL) lyt_treeopts.AddSpacer(5) lyt_treeopts.Add(self.chk_individuals, 0, lyt.PAD_LR, 5) lyt_treeopts.Add(self.chk_preserve_top, 0, lyt.PAD_LR, 5) lyt_treeopts.Add(self.chk_nofollow_symlink, 0, lyt.PAD_LR, 5) lyt_treeopts.AddSpacer(5) pnl_treeopts.SetSizer(lyt_treeopts) lyt_left = BoxSizer(wx.VERTICAL) lyt_left.AddSpacer(10) lyt_left.Add(wx.StaticText(self, label=GT(u'Directory options')), 0, wx.ALIGN_BOTTOM) lyt_left.Add(pnl_treeopts, 0, wx.EXPAND|wx.ALIGN_LEFT|wx.BOTTOM, 5) lyt_left.Add(self.tree_dirs, 1, wx.EXPAND) lyt_target = wx.GridSizer(3, 2, 5, 5) for item in self.grp_targets: lyt_target.Add(item, 0, lyt.PAD_LR, 5) pnl_target.SetAutoLayout(True) pnl_target.SetSizer(lyt_target) pnl_target.Layout() # Put text input in its own sizer to force expand lyt_input = BoxSizer(wx.HORIZONTAL) lyt_input.Add(self.ti_target, 1, wx.ALIGN_CENTER_VERTICAL) lyt_buttons = BoxSizer(wx.HORIZONTAL) lyt_buttons.Add(btn_add, 0) lyt_buttons.Add(btn_remove, 0) lyt_buttons.Add(btn_clear, 0) lyt_buttons.Add(lyt_input, 1, wx.ALIGN_CENTER_VERTICAL) lyt_buttons.Add(self.btn_browse, 0) lyt_buttons.Add(btn_refresh, 0) lyt_right = BoxSizer(wx.VERTICAL) lyt_right.AddSpacer(10) lyt_right.Add(wx.StaticText(self, label=GT(u'Target'))) lyt_right.Add(pnl_target, 0, wx.TOP, 5) lyt_right.Add(lyt_buttons, 0, wx.EXPAND) lyt_right.Add(self.lst_files, 5, wx.EXPAND|wx.TOP, 5) PROP_LEFT = 0 PROP_RIGHT = 1 lyt_main = wx.FlexGridSizer(1, 2) lyt_main.AddGrowableRow(0) # Directory tree size issues with wx 2.8 if wx.MAJOR_VERSION <= 2: PROP_LEFT = 1 lyt_main.AddGrowableCol(0, 1) lyt_main.AddGrowableCol(1, 2) lyt_main.Add(lyt_left, PROP_LEFT, wx.EXPAND|lyt.PAD_LR|wx.BOTTOM, 5) lyt_main.Add(lyt_right, PROP_RIGHT, wx.EXPAND|lyt.PAD_RB, 5) self.SetAutoLayout(True) self.SetSizer(lyt_main) self.Layout() SetPageToolTips(self) ## Adds files to file list # # \param dirs # <b><i>dict</i></b>: dict[dir] = [file list] # \param fileCount # Number of explicit files being added to list # \param showDialog # If <b><i>True</i></b>, displays a progress dialog def AddPaths(self, dirs, fileCount=None, showDialog=False): target = self.GetTarget() if fileCount == None: fileCount = 0 for D in dirs: for F in dirs[D]: fileCount += 1 progress = None Logger.Debug(__name__, u'Adding {} files ...'.format(fileCount)) if showDialog: progress = ProgressDialog(GetMainWindow(), GT(u'Adding Files'), maximum=fileCount, style=PD_DEFAULT_STYLE|wx.PD_CAN_ABORT) progress.Show() completed = 0 for D in sorted(dirs): for F in sorted(dirs[D]): if progress and progress.WasCancelled(): progress.Destroy() return False if progress: wx.Yield() progress.Update(completed, GT(u'Adding file {}').format(F)) self.lst_files.AddFile(F, D, target) completed += 1 if progress: wx.Yield() progress.Update(completed) progress.Destroy() return True ## TODO: Doxygen def CheckDest(self, event=None): if TextIsEmpty(self.ti_target.GetValue()): self.ti_target.SetValue(self.prev_dest_value) self.ti_target.SetInsertionPoint(-1) elif self.ti_target.GetValue()[0] != u'/': self.ti_target.SetValue(self.prev_dest_value) self.ti_target.SetInsertionPoint(-1) if event: event.Skip() ## Retrieves information on files to be packaged # # \return # A list of files with their targets formatted for text output def Get(self): # Remove section delimeters & first line which is just an integer return self.GetSaveData().split(u'\n')[2:-1] ## Retrieves target destination set by user input # # TODO: Rename to 'GetTarget' or 'GetInputTarget' def GetDestValue(self, event=None): if not TextIsEmpty(self.ti_target.GetValue()): if self.ti_target.GetValue()[0] == u'/': self.prev_dest_value = self.ti_target.GetValue() if event: event.Skip() ## Retrieves the directory tree object used by this page # # Used in input.list.FileList for referencing size # # \return # <b><i>ui.tree.DirectoryTreePanel</i></b> instance def GetDirTreePanel(self): return self.tree_dirs ## Retrieves number of files in list # # \return # <b><i>Integer</i></b> count of items in file list def GetFileCount(self): return self.lst_files.GetItemCount() ## Retrieves the file list object used by this page # # \return # <b><i>input.list.FileList</i></b> instance def GetListInstance(self): return self.lst_files ## Retrieves file list to export to text file # # \return # List formatted text def GetSaveData(self): file_list = [] item_count = self.lst_files.GetItemCount() if item_count > 0: count = 0 while count < item_count: filename = self.lst_files.GetItemText(count) source = self.lst_files.GetItem(count, columns.SOURCE).GetText() target = self.lst_files.GetItem(count, columns.TARGET).GetText() absolute_filename = ConcatPaths((source, filename)) # Populate list with tuples of ('src', 'file', 'dest') if self.lst_files.GetItemTextColour(count) == (255, 0, 0): # Mark file as executable file_list.append((u'{}*'.format(absolute_filename), filename, target)) else: file_list.append((absolute_filename, filename, target)) count += 1 return_list = [] for F in file_list: f0 = u'{}'.encode(u'utf-8').format(F[0]) f1 = u'{}'.encode(u'utf-8').format(F[1]) f2 = u'{}'.encode(u'utf-8').format(F[2]) return_list.append(u'{} -> {} -> {}'.format(f0, f1, f2)) return u'<<FILES>>\n1\n{}\n<</FILES>>'.format(u'\n'.join(return_list)) else: # Place a "0" in FILES field if we are not saving any files return u'<<FILES>>\n0\n<</FILES>>' ## Retrieves the target output directory # # FIXME: Duplicate of wizbin.files.Page.GetDestValue? def GetTarget(self): if FieldEnabled(self.ti_target): return self.ti_target.GetValue() for target in self.grp_targets: if target.GetId() != inputid.CUSTOM and target.GetValue(): return target.GetLabel() ## Accepts a file path to read & parse to fill the page's fields # # \param filename # Absolute path of formatted text file to read def ImportFromFile(self, filename): Logger.Debug(__name__, GT(u'Importing page info from {}').format(filename)) if not os.path.isfile(filename): return dbrerrno.ENOENT files_data = ReadFile(filename, split=True) # Lines beginning with these characters will be ignored ignore_characters = ( u'', u' ', u'#', ) target = None targets_list = [] for L in files_data: if not TextIsEmpty(L) and L[0] not in ignore_characters: if u'[' in L and u']' in L: target = L.split(u'[')[-1].split(u']')[0] continue if target: executable = (len(L) > 1 and L[-2:] == u' *') if executable: L = L[:-2] targets_list.append((target, L, executable)) missing_files = [] for T in targets_list: # FIXME: Create method in FileList class to retrieve all missing files if not os.path.exists(T[1]): missing_files.append(T[1]) source_file = os.path.basename(T[1]) source_dir = os.path.dirname(T[1]) self.lst_files.AddFile(source_file, source_dir, T[0], executable=T[2]) if len(missing_files): main_window = GetMainWindow() err_line1 = GT(u'The following files/folders are missing from the filesystem.') err_line2 = GT(u'They will be highlighted on the Files page.') DetailedMessageDialog(main_window, title=GT(u'Warning'), icon=ICON_ERROR, text=u'\n'.join((err_line1, err_line2)), details=u'\n'.join(missing_files)).ShowModal() return 0 ## Checks if the page is ready for export/build # # \return # <b><i>True</i></b> if the file list (self.lst_files) is not empty def IsOkay(self): return not self.lst_files.IsEmpty() ## Reads files & directories & preps for loading into list # # \param pathsList # <b><i>List/Tuple</i></b> of <b><i>string</i></b> values representing # files & directories to be added # \return # Value of wizbin.files.Page.AddPaths, or <b><i>False</i></b> in case of error def LoadPaths(self, pathsList): if isinstance(pathsList, tuple): pathsList = list(pathsList) if not pathsList or not isinstance(pathsList, list): return False file_list = [] dir_list = {} prep = ProgressDialog(GetMainWindow(), GT(u'Processing Files'), GT(u'Scanning files ...'), style=wx.PD_APP_MODAL|wx.PD_AUTO_HIDE|wx.PD_CAN_ABORT) # Only update the gauge every N files (hack until I figure out timer) update_interval = 450 count = 0 prep.Show() if not self.chk_preserve_top.GetValue(): for INDEX in reversed(range(len(pathsList))): path = pathsList[INDEX] if os.path.isdir(path): # Remove top-level directory from list pathsList.pop(INDEX) insert_index = INDEX for P in os.listdir(path): pathsList.insert(insert_index, ConcatPaths((path, P))) insert_index += 1 try: for P in pathsList: if prep.WasCancelled(): prep.Destroy() return False count += 1 if count >= update_interval: wx.Yield() prep.Pulse() count = 0 if not self.chk_individuals.GetValue() or os.path.isfile(P): file_list.append(P) continue if os.path.isdir(P): parent_dir = os.path.dirname(P) if parent_dir not in dir_list: dir_list[parent_dir] = [] for ROOT, DIRS, FILES in os.walk(P): if prep.WasCancelled(): prep.Destroy() return False wx.Yield() prep.SetMessage(GT(u'Scanning directory {} ...').format(ROOT)) count += 1 if count >= update_interval: wx.Yield() prep.Pulse() count = 0 for F in FILES: if prep.WasCancelled(): prep.Destroy() return False count += 1 if count >= update_interval: wx.Yield() prep.Pulse() count = 0 # os.path.dirname preserves top level directory ROOT = ROOT.replace(os.path.dirname(P), u'').strip(u'/') F = u'{}/{}'.format(ROOT, F).strip(u'/') if F not in dir_list[parent_dir]: dir_list[parent_dir].append(F) except: prep.Destroy() ShowErrorDialog(GT(u'Could not retrieve file list'), traceback.format_exc()) return False wx.Yield() prep.Pulse(GT(u'Counting Files')) file_count = len(file_list) count = 0 for D in dir_list: for F in dir_list[D]: file_count += 1 count += 1 if count >= update_interval: wx.Yield() prep.Pulse() count = 0 prep.Destroy() # Add files to directory list for F in file_list: f_name = os.path.basename(F) f_dir = os.path.dirname(F) if f_dir not in dir_list: dir_list[f_dir] = [] dir_list[f_dir].append(f_name) if file_count > warning_threshhold: count_warnmsg = GT(u'Importing {} files'.format(file_count)) count_warnmsg = u'{}. {}.'.format(count_warnmsg, GT(u'This could take a VERY long time')) count_warnmsg = u'{}\n{}'.format(count_warnmsg, GT(u'Are you sure you want to continue?')) if not ConfirmationDialog(GetMainWindow(), text=count_warnmsg).Confirmed(): return False return self.AddPaths(dir_list, file_count, showDialog=file_count >= efficiency_threshold) ## Handles event emitted by 'browse' button # # Opens a directory dialog to select a custom output target def OnBrowse(self, event=None): dia = GetDirDialog(GetMainWindow(), GT(u'Choose Target Directory')) if ShowDialog(dia): self.ti_target.SetValue(dia.GetPath()) ## Handles event emitted by 'clear' button # # Displays confirmation dialog to clear list if not empty # # TODO: Rename to OnClearList? def OnClearFileList(self, event=None): if self.lst_files.GetItemCount(): if ConfirmationDialog(GetMainWindow(), GT(u'Confirm'), GT(u'Clear all files?')).Confirmed(): self.lst_files.DeleteAllItems() ## Adds files to list from file manager drop # # Note that this method should not be renamed as 'OnDropFiles' # is the implicit handler for wx.FileDropTarget (<- correct class???) # # \param fileList # <b><i>List</i></b> of files dropped from file manager # \return # Value of wizbin.files.Page.LoadPaths def OnDropFiles(self, fileList): return self.LoadPaths(fileList) ## Handles files & directories added from ui.tree.DirectoryTreePanel object # (self.tree_dirs) # # Actually bypasses DirectoryTreePanel & directly accesses # ui.tree.DirectoryTree.GetSelectedPaths def OnImportFromTree(self, event=None): return self.LoadPaths(self.DirTree.GetSelectedPaths()) ## Updates files' status in the file list # # Refreshes files' executable & available status # # \return # Value of self.lst_files.RefreshFileList def OnRefreshFileList(self, event=None): return self.lst_files.RefreshFileList() ## Handles event emitted by 'remove' button # # Removes all currently selected/highlighted files in list def OnRemoveSelected(self, event=None): try: modifier = event.GetModifiers() keycode = event.GetKeyCode() except AttributeError: keycode = event.GetEventObject().GetId() if keycode in (wx.ID_REMOVE, wx.WXK_DELETE): self.lst_files.RemoveSelected() elif keycode == 65 and modifier == wx.MOD_CONTROL: self.lst_files.SelectAll() ## Handles enabling/disabling the custom target field if the corresponding # when a target radio button is selected # # TODO: Rename to 'OnSetTarget' or 'OnSelectTarget' def OnSetDestination(self, event=None): enable = self.rb_custom.GetValue() self.ti_target.Enable(enable) self.btn_browse.Enable(enable) ## Resets page's fields to default values # # \return # Value of self.lst_files.Reset def Reset(self): return self.lst_files.Reset() ## Selects all files in the list # # \return # Value of self.lst_files.SelectAll def SelectAll(self): return self.lst_files.SelectAll() ## Sets the page's fields # # \param data # The text information to parse # \return # <b><i>True</i></b> if the data was imported correctly def Set(self, data): # Clear files list self.lst_files.DeleteAllItems() files_data = data.split(u'\n') if int(files_data[0]): # Get file count from list minus first item "1" files_total = len(files_data) # Store missing files here missing_files = [] progress = None if files_total >= efficiency_threshold: progress = ProgressDialog(GetMainWindow(), GT(u'Adding Files'), maximum=files_total, style=PD_DEFAULT_STYLE|wx.PD_CAN_ABORT) wx.Yield() progress.Show() current_file = files_total while current_file > 1: if progress and progress.WasCancelled(): progress.Destroy() # Project continues opening even if file import is cancelled msg = ( GT(u'File import did not complete.'), GT(u'Project files may be missing in file list.'), ) ShowMessageDialog(u'\n'.join(msg), GT(u'Import Cancelled')) return False current_file -= 1 executable = False file_info = files_data[current_file].split(u' -> ') absolute_filename = file_info[0] if absolute_filename[-1] == u'*': # Set executable flag and remove "*" executable = True absolute_filename = absolute_filename[:-1] filename = file_info[1] source_dir = absolute_filename[:len(absolute_filename) - len(filename)] target_dir = file_info[2] if not self.lst_files.AddFile(filename, source_dir, target_dir, executable): Logger.Warn(__name__, GT(u'File not found: {}').format(absolute_filename)) missing_files.append(absolute_filename) if progress: update_value = files_total - current_file wx.Yield() progress.Update(update_value+1, GT(u'Imported file {} of {}').format(update_value, files_total)) if progress: progress.Destroy() Logger.Debug(__name__, u'Missing file count: {}'.format(len(missing_files))) # If files are missing show a message if missing_files: alert = DetailedMessageDialog(GetMainWindow(), GT(u'Missing Files'), ICON_EXCLAMATION, GT(u'Could not locate the following files:'), u'\n'.join(missing_files)) alert.ShowModal() return True
class Page(WizardPage): ## Constructor # # \param parent # Parent <b><i>wx.Window</i></b> instance def __init__(self, parent): WizardPage.__init__(self, parent, pgid.BUILD) # Bypass build prep check self.prebuild_check = False # Add checkable items to this list # FIXME: Use a different method self.build_options = [] # ----- Extra Options pnl_options = BorderedPanel(self) self.chk_md5 = CheckBoxESS(pnl_options, chkid.MD5, GT(u'Create md5sums file'), name=u'MD5', defaultValue=True, commands=u'md5sum') # The » character denotes that an alternate tooltip should be shown if the control is disabled self.chk_md5.tt_name = u'md5»' self.chk_md5.col = 0 if UsingTest(u'alpha'): # Brings up control file preview for editing self.chk_editctrl = CheckBoxCFG( pnl_options, chkid.EDIT, GT(u'Preview control file for editing'), name=u'editctrl') self.chk_editctrl.col = 1 # TODO: Use CheckBoxCFG instead of CheckBoxESS: # Fields will be set from config instead of project file # Option to strip binaries self.chk_strip = CheckBoxESS(pnl_options, chkid.STRIP, GT(u'Strip binaries'), name=u'strip»', defaultValue=True, commands=u'strip') self.chk_strip.col = 0 # Deletes the temporary build tree self.chk_rmstage = CheckBoxESS(pnl_options, chkid.DELETE, GT(u'Delete staged directory'), name=u'RMSTAGE', defaultValue=True) self.chk_rmstage.col = 0 # Checks the output .deb for errors self.chk_lint = CheckBoxESS( pnl_options, chkid.LINT, GT(u'Check package for errors with lintian'), name=u'LINTIAN', defaultValue=True, commands=u'lintian') self.chk_lint.tt_name = u'lintian»' self.chk_lint.col = 0 # Installs the deb on the system self.chk_install = CheckBox(pnl_options, chkid.INSTALL, GT(u'Install package after build'), name=u'INSTALL', commands=( u'gdebi-gtk', u'gdebi-kde', )) self.chk_install.tt_name = u'install»' self.chk_install.col = 0 # *** Lintian Overrides *** # if UsingTest(u'alpha'): # FIXME: Move next to lintian check box self.lint_overrides = [] btn_lint_overrides = CreateButton(self, label=GT(u'Lintian overrides')) btn_lint_overrides.Bind(wx.EVT_BUTTON, self.OnSetLintOverrides) btn_build = CreateButton(self, btnid.BUILD, GT(u'Build'), u'build', 64) # Display log dsp_log = OutputLog(self) SetPageToolTips(self) # *** Event Handling *** # btn_build.Bind(wx.EVT_BUTTON, self.OnBuild) # *** Layout *** # lyt_options = wx.GridBagSizer() next_row = 0 prev_row = next_row for CHK in pnl_options.Children: row = next_row FLAGS = lyt.PAD_LR if CHK.col: row = prev_row FLAGS = wx.RIGHT lyt_options.Add(CHK, (row, CHK.col), flag=FLAGS, border=5) if not CHK.col: prev_row = next_row next_row += 1 pnl_options.SetSizer(lyt_options) pnl_options.SetAutoLayout(True) pnl_options.Layout() lyt_buttons = BoxSizer(wx.HORIZONTAL) lyt_buttons.Add(btn_build, 1) lyt_main = BoxSizer(wx.VERTICAL) lyt_main.AddSpacer(10) lyt_main.Add(wx.StaticText(self, label=GT(u'Extra Options')), 0, lyt.ALGN_LB | wx.LEFT, 5) lyt_main.Add(pnl_options, 0, wx.LEFT, 5) lyt_main.AddSpacer(5) if UsingTest(u'alpha'): #lyt_main.Add(wx.StaticText(self, label=GT(u'Lintian overrides')), 0, wx.LEFT, 5) lyt_main.Add(btn_lint_overrides, 0, wx.LEFT, 5) lyt_main.AddSpacer(5) lyt_main.Add(lyt_buttons, 0, lyt.ALGN_C) lyt_main.Add(dsp_log, 2, wx.EXPAND | lyt.PAD_LRB, 5) self.SetAutoLayout(True) self.SetSizer(lyt_main) self.Layout() # *** Post-layout functions *** # self.InitDefaultSettings() ## Method that builds the actual Debian package # # TODO: Test for errors when building deb package with other filename extension # TODO: Remove deprecated methods that this one replaces # \param outFile # \b \e str|unicode : Absolute path to target file def Build(self, outFile): def log_message(msg, current_step, total_steps): return u'{} ({}/{})'.format(msg, current_step, total_steps) wizard = GetWizard() pages_build_ids = self.BuildPrep() if pages_build_ids != None: main_window = GetMainWindow() # Reported at the end of build build_summary = [] steps_count = len(pages_build_ids) current_step = 0 # Steps from build page for chk in self.chk_md5, self.chk_lint, self.chk_rmstage: if chk.IsChecked(): steps_count += 1 # Control file & .deb build step steps_count += 2 stage = CreateStage() log_msg = GT(u'Starting build') wx.YieldIfNeeded() # FIXME: Enable PD_CAN_ABORT build_progress = ProgressDialog(main_window, GT(u'Building'), log_msg, maximum=steps_count) build_summary.append(u'{}:'.format(log_msg)) try: for P in wizard.GetAllPages(): if build_progress.WasCancelled(): break if P.GetId() in pages_build_ids: p_label = P.GetTitle() log_msg = log_message( GT(u'Processing page "{}"').format(p_label), current_step + 1, steps_count) # FIXME: Progress bar not updating??? wx.YieldIfNeeded() build_progress.Update(current_step, log_msg) ret_code, ret_value = P.ExportBuild(stage) build_summary.append(u'\n{}:\n{}'.format( log_msg, ret_value)) if ret_code > 0: build_progress.Destroy() ShowErrorDialog(GT(u'Error occurred during build'), ret_value) return current_step += 1 # *** Control File *** # if not build_progress.WasCancelled(): wx.YieldIfNeeded() log_msg = log_message(GT(u'Creating control file'), current_step + 1, steps_count) build_progress.Update(current_step, log_msg) Logger.Debug(__name__, log_msg) # Retrieve control page pg_control = wizard.GetPage(pgid.CONTROL) if not pg_control: build_progress.Destroy() ShowErrorDialog( GT(u'Could not retrieve control page'), GT(u'Please contact the developer: {}').format( AUTHOR_email), title=u'Fatal Error') return installed_size = self.OnBuildGetInstallSize(stage) Logger.Debug( __name__, GT(u'Installed size: {}').format(installed_size)) build_summary.append(u'\n{}:'.format(log_msg)) build_summary.append( pg_control.ExportBuild( u'{}/DEBIAN'.format(stage).replace(u'//', u'/'), installed_size)) current_step += 1 # *** MD5 Checksum *** # if not build_progress.WasCancelled(): if self.chk_md5.GetValue() and GetExecutable(u'md5sum'): log_msg = log_message(GT(u'Creating MD5 checksum'), current_step + 1, steps_count) #log_msg = GT(u'Creating MD5 checksum') #step = u'{}/{}'.format(current_step+1, steps_count) Logger.Debug(__name__, log_msg) wx.YieldIfNeeded() build_progress.Update(current_step, log_msg) build_summary.append(u'\n{}:'.format(log_msg)) build_summary.append(self.OnBuildMD5Sum(stage)) current_step += 1 # *** Create .deb from Stage *** # if not build_progress.WasCancelled(): log_msg = log_message(GT(u'Creating .deb package'), current_step + 1, steps_count) wx.YieldIfNeeded() build_progress.Update(current_step, log_msg) build_summary.append(u'\n{}:'.format(log_msg)) build_summary.append( self.OnBuildCreatePackage(stage, outFile)) current_step += 1 # *** Lintian *** # if not build_progress.WasCancelled(): if self.chk_lint.IsChecked(): log_msg = log_message( GT(u'Checking package with lintian'), current_step + 1, steps_count) wx.YieldIfNeeded() build_progress.Update(current_step, log_msg) build_summary.append(u'\n{}:'.format(log_msg)) build_summary.append(self.OnBuildCheckPackage(outFile)) current_step += 1 # *** Delete Stage *** # if not build_progress.WasCancelled(): if self.chk_rmstage.IsChecked(): log_msg = log_message( GT(u'Removing staged build tree'), current_step + 1, steps_count) wx.YieldIfNeeded() build_progress.Update(current_step, log_msg) build_summary.append(u'\n{}:'.format(log_msg)) RemoveStage(stage) if not os.path.isdir(stage): build_summary.append( GT(u'Staged build tree removed successfully')) else: build_summary.append( GT(u'Failed to remove staged build tree')) current_step += 1 # *** Show Completion Status *** # wx.YieldIfNeeded() build_progress.Update(steps_count, GT(u'Build completed')) # Show finished dialog for short moment time.sleep(1) # TODO: Add error count to build summary build_progress.Destroy() build_summary = u'\n'.join(build_summary) summary_dialog = DetailedMessageDialog(main_window, GT(u'Build Summary'), ICON_INFORMATION, GT(u'Build completed'), build_summary) summary_dialog.ShowModal() except: build_progress.Destroy() ShowErrorDialog(GT(u'Error occurred during build'), traceback.format_exc()) return ## Checks pages for export & gets a count of how many tasks need be executed # # \return # <b><i>Tuple</i></b> containing data & label for each page def BuildPrep(self): wizard = GetWizard() prep_ids = [] pages = wizard.GetAllPages() for P in pages: if P.prebuild_check: Logger.Debug( __name__, GT(u'Pre-build check for page "{}"'.format(P.GetName()))) prep_ids.append(P.GetId()) try: main_window = GetMainWindow() # List of page IDs to process during build pg_build_ids = [] steps_count = len(prep_ids) current_step = 0 msg_label1 = GT(u'Prepping page "{}"') msg_label2 = GT(u'Step {}/{}') msg_label = u'{} ({})'.format(msg_label1, msg_label2) prep_progress = ProgressDialog( main_window, GT(u'Preparing Build'), msg_label2.format(current_step, steps_count), maximum=steps_count, style=PD_DEFAULT_STYLE | wx.PD_CAN_ABORT) for P in pages: if prep_progress.WasCancelled(): break p_id = P.GetId() p_label = P.GetTitle() if p_id in prep_ids: Logger.Debug( __name__, msg_label.format(p_label, current_step + 1, steps_count)) wx.Yield() prep_progress.Update( current_step, msg_label.format(p_label, current_step + 1, steps_count)) if P.IsOkay(): pg_build_ids.append(p_id) current_step += 1 if not prep_progress.WasCancelled(): wx.Yield() prep_progress.Update(current_step, GT(u'Prepping finished')) # Show finished dialog for short period time.sleep(1) prep_progress.Destroy() return pg_build_ids except: prep_progress.Destroy() ShowErrorDialog(GT(u'Error occurred during pre-build'), traceback.format_exc()) return None ## Preview control file for editing def EditControl(self): pg_control = GetPage(pgid.CONTROL) ctrl_info = pg_control.GetCtrlInfo() preview = TextPreview(title=GT(u'Edit Control File'), text=ctrl_info, size=(600, 400), readonly=False) AddCustomButtons(preview, ( wx.ID_SAVE, wx.ID_CANCEL, ), parent_sizer=True) if preview.ShowModal() == wx.ID_SAVE: Logger.Debug(__name__, u'Updating control information ...') ctrl_info = preview.GetValue() depends_data = pg_control.Set(ctrl_info) GetPage(pgid.DEPENDS).Set(depends_data) ## Retrieves page data from fields def Get(self, getModule=False): # 'install after build' is not exported to project for safety fields = {} omit_options = (self.chk_install, ) for O in self.build_options: # Leave options out that should not be saved if O not in omit_options: fields[O.GetName()] = GS(O.GetValue()) page = wx.EmptyString for F in fields: if page == wx.EmptyString: page = u'{}={}'.format(F, fields[F]) else: page = u'{}\n{}={}'.format(page, F, fields[F]) if page == wx.EmptyString: page = None if getModule: page = ( __name__, page, ) return page ## Reads & parses page data from a formatted text file def ImportFromFile(self, filename): if not os.path.isfile(filename): return dbrerrno.ENOENT build_data = ReadFile(filename, split=True) options_definitions = {} for L in build_data: if u'=' in L: key = L.split(u'=') value = GetBoolean(key[-1]) key = key[0] options_definitions[key] = value for O in self.build_options: name = O.GetName() if name in options_definitions and isinstance( options_definitions[name], bool): O.SetValue(options_definitions[name]) return 0 ## Sets up page with default settings # # FIXME: Deprecated/Replace with 'Reset' method??? def InitDefaultSettings(self): self.build_options = [] option_list = ( ( self.chk_md5, GetExecutable(u'md5sum'), ), ( self.chk_strip, GetExecutable(u'strip'), ), ( self.chk_rmstage, True, ), ( self.chk_lint, GetExecutable(u'lintian'), ), ( self.chk_install, GetSystemInstaller(), ), ) for option, command in option_list: # FIXME: Commands should be updated globally if not isinstance(command, bool): command = CommandExists(command) option.Enable(bool(command)) option.SetValue(FieldEnabled(option) and option.Default) if bool(command): self.build_options.append(option) ## Installs the built .deb package onto the system # # Uses the system's package installer: gdebi-gtk or gdebi-kde if available # # Shows a success dialog if installed. Otherwise shows an # error dialog. # # \param package # Path of package to be installed def InstallPackage(self, package): system_installer = GetSystemInstaller() if not system_installer: ShowErrorDialog( GT(u'Cannot install package'), GT(u'A compatible package manager could not be found on the system' ), __name__, warn=True) return Logger.Info(__name__, GT(u'Attempting to install package: {}').format(package)) Logger.Info(__name__, GT(u'Installing with {}').format(system_installer)) install_cmd = ( system_installer, package, ) wx.Yield() # FIXME: Use ExecuteCommand here install_output = subprocess.Popen(install_cmd) # Command appears to not have been executed correctly if install_output == None: ShowErrorDialog(GT(u'Could not install package: {}'), GT(u'An unknown error occurred'), __name__) return # Command executed but did not return success code if install_output.returncode: err_details = ( GT(u'Process returned code {}').format( install_output.returncode), GT(u'Command executed: {}').format(u' '.join(install_cmd)), ) ShowErrorDialog(GT(u'An error occurred during installation'), u'\n'.join(err_details), __name__) return ## Handles event emitted by the 'build' button # # Checks if required fields are filled & opens a file save dialog def OnBuild(self, event=None): if event: event.Skip() # Show control file preview for editing if UsingTest(u'alpha') and self.chk_editctrl.GetValue(): self.EditControl() wizard = GetWizard() pg_control = wizard.GetPage(pgid.CONTROL) pg_files = wizard.GetPage(pgid.FILES) pg_launcher = wizard.GetPage(pgid.LAUNCHERS) required_fields = { GT(u'Control'): pg_control.GetRequiredFields(), } # Check if launchers are enabled for build if pg_launcher.GetLaunchersCount(): required_fields[GT( u'Menu Launcher')] = pg_launcher.GetRequiredFields() # FIXME: Old code won't work with multiple launchers for RF in required_fields[GT(u'Menu Launcher')]: Logger.Debug( __name__, GT(u'Required field (Menu Launcher): {}').format( RF.GetName())) for p_name in required_fields: Logger.Debug(__name__, GT(u'Page name: {}').format(p_name)) for F in required_fields[p_name]: if not isinstance(F, wx.StaticText) and TextIsEmpty( F.GetValue()): f_name = F.GetName() msg_l1 = GT(u'One of the required fields is empty:') msg_full = u'{}: {} ➜ {}'.format(msg_l1, p_name, f_name) Logger.Warn(__name__, msg_full) DetailedMessageDialog(GetMainWindow(), GT(u'Cannot Continue'), ICON_EXCLAMATION, text=msg_full).ShowModal() for P in wizard.GetAllPages(): if P.GetTitle() == p_name: Logger.Debug( __name__, GT(u'Showing page with required field: {}'). format(p_name)) wizard.ShowPage(P.GetId()) return if GetField(pg_files, inputid.LIST).MissingFiles(): ShowErrorDialog(GT(u'Files are missing in file list'), warn=True, title=GT(u'Warning')) wizard.ShowPage(pgid.FILES) return ttype = GT(u'Debian Packages') save_dialog = GetFileSaveDialog(GetMainWindow(), GT(u'Build Package'), u'{} (*.deb)|*.deb'.format(ttype), u'deb') package = GetFieldValue(pg_control, inputid.PACKAGE) version = GetFieldValue(pg_control, inputid.VERSION) arch = GetFieldValue(pg_control, inputid.ARCH) save_dialog.SetFilename(u'{}_{}_{}.deb'.format(package, version, arch)) if ShowDialog(save_dialog): self.Build(save_dialog.GetPath()) ## Checks the final package for error with the lintian command # # \param targetPackage # Path of package to check def OnBuildCheckPackage(self, targetPackage): Logger.Debug( __name__, GT(u'Checking package "{}" for lintian errors ...').format( os.path.basename(targetPackage))) # FIXME: commands module deprecated? output = commands.getoutput(u'{} "{}"'.format( GetExecutable(u'lintian'), targetPackage)) return output ## Handles the process of building the package from the formatted stage directory # # \param stage # Path to formatted temporary staged directory # \param targetFile # Path of the target output .deb package def OnBuildCreatePackage(self, stage, targetFile): Logger.Debug(__name__, GT(u'Creating {} from {}').format(targetFile, stage)) packager = GetExecutable(u'dpkg-deb') fakeroot = GetExecutable(u'fakeroot') if not fakeroot or not packager: return (dbrerrno.ENOENT, GT(u'Cannot run "fakeroot dpkg')) packager = os.path.basename(packager) Logger.Debug(__name__, GT(u'System packager: {}').format(packager)) # DEBUG: cmd = u'{} {} -b "{}" "{}"'.format(fakeroot, packager, stage, targetFile) Logger.Debug(__name__, GT(u'Executing: {}').format(cmd)) output = GetCommandOutput(fakeroot, ( packager, u'-b', stage, targetFile, )) Logger.Debug(__name__, GT(u'Build output: {}').format(output)) return output ## Retrieves total size of directory contents # # TODO: Move this method to control page??? # # \param stage # Path to formatted staged directory to scan file sizes # \return # <b><i>Integer</i></b> value representing installed size of all files in package def OnBuildGetInstallSize(self, stage): Logger.Debug(__name__, GT(u'Retrieving installed size for {}').format(stage)) installed_size = 0 for ROOT, DIRS, FILES in os.walk(stage): for F in FILES: if ROOT != u'{}/DEBIAN'.format(stage).replace(u'//', u'/'): F = u'{}/{}'.format(ROOT, F).replace(u'//', u'/') installed_size += os.stat(F).st_size # Convert to kilobytes & round up if installed_size: installed_size = int(math.ceil( float(installed_size) / float(1024))) return installed_size ## Creates an 'md5sum' file & populates with hashes for all files contained in package # # FIXME: Hashes for .png images (binary files???) is not the same as those # produced by debuild # # \param stage # Staged directory where files are scanned # \return # <b><i>String</i></b> message of result def OnBuildMD5Sum(self, stage): Logger.Debug(__name__, GT(u'Creating MD5sum file in {}').format(stage)) WriteMD5(stage) return GT(u'md5sums file created: {}'.format( os.path.isfile(ConcatPaths(( stage, u'DEBIAN/md5sums', ))))) ## Retrieves list of available lintian tags (WIP) # # TODO: Show warning dialog that this could take a while # TODO: Add cancel option to progress dialog # FIXME: List should be cached so no need for re-scanning def OnSetLintOverrides(self, event=None): Logger.Debug(__name__, GT(u'Setting Lintian overrides...')) lintian_tags_file = u'{}/data/lintian/tags'.format(PATH_app) if not os.path.isfile(lintian_tags_file): Logger.Error( __name__, u'Lintian tags file is missing: {}'.format(lintian_tags_file)) return False lint_tags = RemoveEmptyLines(ReadFile(lintian_tags_file, split=True)) if lint_tags: Logger.Debug(__name__, u'Lintian tags set') # DEBUG: Start if DebugEnabled() and len(lint_tags) > 50: print(u' Reducing tag count to 200 ...') lint_tags = lint_tags[:50] Logger.Debug(__name__, u'Processing {} tags'.format(len(lint_tags))) # DEBUG: End tag_count = len(lint_tags) def GetProgressMessage(message, count=tag_count): return u'{} ({} {})'.format(message, count, GT(u'tags')) progress = TimedProgressDialog( GetMainWindow(), GT(u'Building Tag List'), GetProgressMessage(GT(u'Scanning default tags'))) progress.Start() wx.Yield() # Create the dialog overrides_dialog = CheckListDialog(GetMainWindow(), title=GT(u'Lintian Overrides'), allow_custom=True) # FIXME: Needs progress dialog overrides_dialog.InitCheckList(tuple(lint_tags)) progress.SetMessage( GetProgressMessage(GT(u'Setting selected overrides'))) for T in lint_tags: if T in self.lint_overrides: overrides_dialog.SetItemCheckedByLabel(T) self.lint_overrides.remove(T) progress.SetMessage( GetProgressMessage(GT(u'Adding custom tags'), len(self.lint_overrides))) # Remaining tags should be custom entries # FIXME: if self.lint_overrides: for T in self.lint_overrides: overrides_dialog.AddItem(T, True) progress.Stop() if overrides_dialog.ShowModal() == wx.ID_OK: # Remove old overrides self.lint_overrides = [] for L in overrides_dialog.GetCheckedLabels(): Logger.Debug(__name__, GT(u'Adding Lintian override: {}').format(L)) self.lint_overrides.append(L) return True else: Logger.Debug(__name__, u'Setting lintian tags failed') return False ## Resets page's fields to default settings def Reset(self): for O in self.build_options: O.SetValue(O.Default) ## Sets page's fields data # # \param data # <b><i>Text</i></b> to be parsed for values def Set(self, data): # ???: Redundant self.Reset() build_data = data.split(u'\n') if GetExecutable(u'md5sum'): self.chk_md5.SetValue(int(build_data[0])) self.chk_rmstage.SetValue(int(build_data[1])) if GetExecutable(u'lintian'): self.chk_lint.SetValue(int(build_data[2])) ## Sets the build summary for display & review after package is created def SetSummary(self, event=None): pg_scripts = GetPage(pgid.SCRIPTS) # Make sure the page is not destroyed so no error is thrown if self: # Set summary when "Build" page is shown # Get the file count files_total = GetPage(pgid.FILES).GetFileCount() f = GT(u'File Count') file_count = u'{}: {}'.format(f, files_total) # Scripts to make scripts_to_make = [] scripts = ((u'preinst', pg_scripts.chk_preinst), (u'postinst', pg_scripts.chk_postinst), (u'prerm', pg_scripts.chk_prerm), (u'postrm', pg_scripts.chk_postrm)) for script in scripts: if script[1].IsChecked(): scripts_to_make.append(script[0]) s = GT(u'Scripts') if len(scripts_to_make): scripts_to_make = u'{}: {}'.format(s, u', '.join(scripts_to_make)) else: scripts_to_make = u'{}: 0'.format(s) self.summary.SetValue(u'\n'.join((file_count, scripts_to_make)))
def __init__(self, parent): WizardPage.__init__(self, parent, pgid.BUILD) # Bypass build prep check self.prebuild_check = False # Add checkable items to this list # FIXME: Use a different method self.build_options = [] # ----- Extra Options pnl_options = BorderedPanel(self) self.chk_md5 = CheckBoxESS(pnl_options, chkid.MD5, GT(u'Create md5sums file'), name=u'MD5', defaultValue=True, commands=u'md5sum') # The » character denotes that an alternate tooltip should be shown if the control is disabled self.chk_md5.tt_name = u'md5»' self.chk_md5.col = 0 if UsingTest(u'alpha'): # Brings up control file preview for editing self.chk_editctrl = CheckBoxCFG( pnl_options, chkid.EDIT, GT(u'Preview control file for editing'), name=u'editctrl') self.chk_editctrl.col = 1 # TODO: Use CheckBoxCFG instead of CheckBoxESS: # Fields will be set from config instead of project file # Option to strip binaries self.chk_strip = CheckBoxESS(pnl_options, chkid.STRIP, GT(u'Strip binaries'), name=u'strip»', defaultValue=True, commands=u'strip') self.chk_strip.col = 0 # Deletes the temporary build tree self.chk_rmstage = CheckBoxESS(pnl_options, chkid.DELETE, GT(u'Delete staged directory'), name=u'RMSTAGE', defaultValue=True) self.chk_rmstage.col = 0 # Checks the output .deb for errors self.chk_lint = CheckBoxESS( pnl_options, chkid.LINT, GT(u'Check package for errors with lintian'), name=u'LINTIAN', defaultValue=True, commands=u'lintian') self.chk_lint.tt_name = u'lintian»' self.chk_lint.col = 0 # Installs the deb on the system self.chk_install = CheckBox(pnl_options, chkid.INSTALL, GT(u'Install package after build'), name=u'INSTALL', commands=( u'gdebi-gtk', u'gdebi-kde', )) self.chk_install.tt_name = u'install»' self.chk_install.col = 0 # *** Lintian Overrides *** # if UsingTest(u'alpha'): # FIXME: Move next to lintian check box self.lint_overrides = [] btn_lint_overrides = CreateButton(self, label=GT(u'Lintian overrides')) btn_lint_overrides.Bind(wx.EVT_BUTTON, self.OnSetLintOverrides) btn_build = CreateButton(self, btnid.BUILD, GT(u'Build'), u'build', 64) # Display log dsp_log = OutputLog(self) SetPageToolTips(self) # *** Event Handling *** # btn_build.Bind(wx.EVT_BUTTON, self.OnBuild) # *** Layout *** # lyt_options = wx.GridBagSizer() next_row = 0 prev_row = next_row for CHK in pnl_options.Children: row = next_row FLAGS = lyt.PAD_LR if CHK.col: row = prev_row FLAGS = wx.RIGHT lyt_options.Add(CHK, (row, CHK.col), flag=FLAGS, border=5) if not CHK.col: prev_row = next_row next_row += 1 pnl_options.SetSizer(lyt_options) pnl_options.SetAutoLayout(True) pnl_options.Layout() lyt_buttons = BoxSizer(wx.HORIZONTAL) lyt_buttons.Add(btn_build, 1) lyt_main = BoxSizer(wx.VERTICAL) lyt_main.AddSpacer(10) lyt_main.Add(wx.StaticText(self, label=GT(u'Extra Options')), 0, lyt.ALGN_LB | wx.LEFT, 5) lyt_main.Add(pnl_options, 0, wx.LEFT, 5) lyt_main.AddSpacer(5) if UsingTest(u'alpha'): #lyt_main.Add(wx.StaticText(self, label=GT(u'Lintian overrides')), 0, wx.LEFT, 5) lyt_main.Add(btn_lint_overrides, 0, wx.LEFT, 5) lyt_main.AddSpacer(5) lyt_main.Add(lyt_buttons, 0, lyt.ALGN_C) lyt_main.Add(dsp_log, 2, wx.EXPAND | lyt.PAD_LRB, 5) self.SetAutoLayout(True) self.SetSizer(lyt_main) self.Layout() # *** Post-layout functions *** # self.InitDefaultSettings()
def __init__(self, parent, win_id=wx.ID_ANY, name=u'launcher'): ScrolledPanel.__init__(self, parent, win_id, name=name) # --- Buttons to open/preview/save .desktop file btn_open = CreateButton(self, btnid.BROWSE, GT(u'Browse'), u'browse', name=u'btn browse') btn_save = CreateButton(self, btnid.SAVE, GT(u'Save'), u'save', name=u'btn save') btn_preview = CreateButton(self, btnid.PREVIEW, GT(u'Preview'), u'preview', name=u'btn preview') # --- TYPE opts_type = ( u'Application', u'Link', u'Directory', ) txt_type = wx.StaticText(self, label=GT(u'Type'), name=u'type') ti_type = ComboBoxESS(self, inputid.TYPE, choices=opts_type, name=u'Type', defaultValue=opts_type[0]) # --- ENCODING opts_enc = ( u'UTF-1', u'UTF-7', u'UTF-8', u'CESU-8', u'UTF-EBCDIC', u'UTF-16', u'UTF-32', u'SCSU', u'BOCU-1', u'Punycode', u'GB 18030', ) txt_enc = wx.StaticText(self, label=GT(u'Encoding'), name=u'encoding') ti_enc = ComboBoxESS(self, inputid.ENC, choices=opts_enc, name=u'Encoding', defaultValue=opts_enc[2]) # --- TERMINAL chk_term = CheckBoxESS(self, chkid.TERM, GT(u'Terminal'), name=u'Terminal') # --- STARTUP NOTIFY chk_notify = CheckBoxESS(self, chkid.NOTIFY, GT(u'Startup Notify'), name=u'StartupNotify', defaultValue=True) # --- NAME (menu) txt_name = wx.StaticText(self, label=GT(u'Name'), name=u'name*') ti_name = TextAreaESS(self, inputid.NAME, name=u'Name') ti_name.req = True # --- EXECUTABLE txt_exec = wx.StaticText(self, label=GT(u'Executable'), name=u'exec') ti_exec = TextAreaESS(self, inputid.EXEC, name=u'Exec') # --- COMMENT txt_comm = wx.StaticText(self, label=GT(u'Comment'), name=u'comment') ti_comm = TextAreaESS(self, inputid.DESCR, name=u'Comment') # --- ICON txt_icon = wx.StaticText(self, label=GT(u'Icon'), name=u'icon') ti_icon = TextAreaESS(self, inputid.ICON, name=u'Icon') txt_mime = wx.StaticText(self, label=GT(u'MIME Type'), name=u'mime') ti_mime = TextAreaESS(self, inputid.MIME, defaultValue=wx.EmptyString, name=u'MimeType', outLabel=u'MimeType') # ----- OTHER/CUSTOM txt_other = wx.StaticText(self, label=GT(u'Custom Fields'), name=u'other') btn_other = CreateButton(self, label=GT(u'Other'), image=u'add', name=u'btn other') btn_rm_other = CreateButton(self, btnid.REMOVE, GT(u'Remove Other'), u'remove', name=u'btn rm other') pnl_other = SectionedPanel(self, inputid.OTHER) btn_rm_other.Enable(pnl_other.HasSelected()) # --- CATEGORIES opts_category = ( u'2DGraphics', u'Accessibility', u'Application', u'ArcadeGame', u'Archiving', u'Audio', u'AudioVideo', u'BlocksGame', u'BoardGame', u'Calculator', u'Calendar', u'CardGame', u'Compression', u'ContactManagement', u'Core', u'DesktopSettings', u'Development', u'Dictionary', u'DiscBurning', u'Documentation', u'Email', u'FileManager', u'FileTransfer', u'Game', u'GNOME', u'Graphics', u'GTK', u'HardwareSettings', u'InstantMessaging', u'KDE', u'LogicGame', u'Math', u'Monitor', u'Network', u'OCR', u'Office', u'P2P', u'PackageManager', u'Photography', u'Player', u'Presentation', u'Printing', u'Qt', u'RasterGraphics', u'Recorder', u'RemoteAccess', u'Scanning', u'Screensaver', u'Security', u'Settings', u'Spreadsheet', u'System', u'Telephony', u'TerminalEmulator', u'TextEditor', u'Utility', u'VectorGraphics', u'Video', u'Viewer', u'WordProcessor', u'Wine', u'Wine-Programs-Accessories', u'X-GNOME-NetworkSettings', u'X-GNOME-PersonalSettings', u'X-GNOME-SystemSettings', u'X-KDE-More', u'X-Red-Hat-Base', u'X-SuSE-ControlCenter-System', ) txt_category = wx.StaticText(self, label=GT(u'Categories'), name=u'category') btn_catclr = CreateButton(self, btnid.CLEAR, GT(u'Clear'), u'clear', name=u'clear category') lst_categories = CheckList(self, listid.CAT, opts_category, name=u'Categories') if not lst_categories.HasSelected(): btn_catclr.Disable() txt_catcustom = wx.StaticText( self, label=GT(u'Custom Categories (Separate by "," or ";")')) # Set to 'True' to list custom categories first # FIXME: Should this be saved to project instead of config??? chk_catcustom = CheckBoxCFG(self, chkid.CAT, GT(u'List first'), name=u'chk catcustom', cfgKey=u'prioritize custom categories') ti_catcustom = TextAreaESS(self, inputid.CAT2, name=u'category custom') # *** Event Handling *** # btn_open.Bind(wx.EVT_BUTTON, self.OnLoadLauncher) btn_save.Bind(wx.EVT_BUTTON, self.OnExportLauncher) btn_preview.Bind(wx.EVT_BUTTON, self.OnPreviewLauncher) btn_other.Bind(wx.EVT_BUTTON, self.OnOtherAdd) btn_rm_other.Bind(wx.EVT_BUTTON, self.OnOtherRemove) btn_catclr.Bind(wx.EVT_BUTTON, self.OnClearCategories) wx.EVT_CHECKBOX(self, inputid.OTHER, self.OnOtherSelect) wx.EVT_CHECKBOX(self, listid.CAT, self.OnCatSelect) # *** Layout *** # LEFT_CENTER = wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL LEFT_BOTTOM = wx.ALIGN_LEFT | wx.ALIGN_BOTTOM RIGHT_BOTTOM = wx.ALIGN_RIGHT | wx.ALIGN_BOTTOM lyt_opts1 = wx.FlexGridSizer() lyt_opts1.SetCols(3) lyt_opts1.SetRows(2) lyt_opts1.Add(txt_type, 0, LEFT_CENTER) lyt_opts1.Add(ti_type, 0, wx.EXPAND | wx.LEFT, 5) lyt_opts1.Add(chk_term, 0, LEFT_CENTER | wx.LEFT, 5) lyt_opts1.Add(txt_enc, 0, LEFT_CENTER | wx.TOP, 5) lyt_opts1.Add(ti_enc, 0, wx.LEFT | wx.TOP, 5) lyt_opts1.Add(chk_notify, 0, LEFT_CENTER | wx.LEFT | wx.TOP, 5) lyt_top = BoxSizer(wx.HORIZONTAL) lyt_top.Add(lyt_opts1, 0, wx.EXPAND | wx.ALIGN_BOTTOM) lyt_top.AddStretchSpacer(1) lyt_top.Add(btn_open, 0, wx.ALIGN_TOP) lyt_top.Add(btn_save, 0, wx.ALIGN_TOP) lyt_top.Add(btn_preview, 0, wx.ALIGN_TOP) lyt_mid = wx.GridBagSizer() lyt_mid.SetCols(4) lyt_mid.AddGrowableCol(1) lyt_mid.AddGrowableCol(3) # Row 1 row = 0 lyt_mid.Add(txt_name, (row, 0), flag=LEFT_CENTER) lyt_mid.Add(ti_name, (row, 1), flag=wx.EXPAND | wx.LEFT, border=5) lyt_mid.Add(txt_exec, (row, 2), flag=LEFT_CENTER | wx.LEFT, border=5) lyt_mid.Add(ti_exec, (row, 3), flag=wx.EXPAND | wx.LEFT, border=5) # Row 2 row += 1 lyt_mid.Add(txt_comm, (row, 0), flag=LEFT_CENTER | wx.TOP, border=5) lyt_mid.Add(ti_comm, (row, 1), flag=wx.EXPAND | wx.LEFT | wx.TOP, border=5) lyt_mid.Add(txt_icon, (row, 2), flag=LEFT_CENTER | wx.LEFT | wx.TOP, border=5) lyt_mid.Add(ti_icon, (row, 3), flag=wx.EXPAND | wx.LEFT | wx.TOP, border=5) # Row 3 row += 1 lyt_mid.Add(txt_mime, (row, 0), flag=LEFT_CENTER | wx.TOP, border=5) lyt_mid.Add(ti_mime, (row, 1), flag=wx.EXPAND | wx.LEFT | wx.TOP, border=5) lyt_bottom = wx.GridBagSizer() row = 0 lyt_bottom.Add(txt_other, (row, 0), flag=LEFT_BOTTOM) lyt_bottom.Add(btn_other, (row, 1), flag=RIGHT_BOTTOM) lyt_bottom.Add(btn_rm_other, (row, 2), flag=RIGHT_BOTTOM) lyt_bottom.Add(txt_category, (row, 3), flag=LEFT_BOTTOM | wx.LEFT, border=5) lyt_bottom.Add(btn_catclr, (row, 4), flag=RIGHT_BOTTOM) row += 1 lyt_bottom.Add(pnl_other, (row, 0), (3, 3), wx.EXPAND) lyt_bottom.Add(lst_categories, (row, 3), (1, 2), wx.EXPAND | wx.LEFT, 5) row += 1 lyt_bottom.Add(txt_catcustom, (row, 3), flag=LEFT_BOTTOM | wx.LEFT | wx.TOP, border=5) lyt_bottom.Add(chk_catcustom, (row, 4), flag=RIGHT_BOTTOM) row += 1 lyt_bottom.Add(ti_catcustom, (row, 3), (1, 2), flag=wx.EXPAND | wx.LEFT, border=5) lyt_bottom.AddGrowableRow(1) lyt_bottom.AddGrowableCol(1) lyt_bottom.AddGrowableCol(3) # --- Page 5 Sizer --- # lyt_main = BoxSizer(wx.VERTICAL) lyt_main.AddSpacer(5) lyt_main.Add(lyt_top, 0, wx.EXPAND | wx.LEFT | wx.RIGHT, 5) lyt_main.Add(lyt_mid, 0, wx.EXPAND | wx.LEFT | wx.RIGHT | wx.TOP, 5) lyt_main.Add(lyt_bottom, 1, wx.EXPAND | wx.ALL, 5) self.SetAutoLayout(True) self.SetSizer(lyt_main) self.Layout()