def capture_image(self, x, y, width, height, window): pb = pixbuf.new(colorspace.RGB, True, 8, width, height) # mask the pixbuf if we have more than one screen root_width, root_height = window.get_width(), window.get_height() pb2 = pixbuf.new(colorspace.RGB, True, 8, root_width, root_height) pb2 = gdk.pixbuf_get_from_window(window, 0, 0, root_width, root_height) pb2 = self.mask_pixbuf(pb2, root_width, root_height) pb2.copy_area(x, y, width, height, pb, 0, 0) if not pb: print('Invalid Pixbuf') exit(EXIT_INVALID_PIXBUF) if self.use_clipboard: self.save_clipboard(pb) else: self.save_file(pb, width, height) # call_exec returns immediately if self.command is None self.call_exec(width, height) # daemonize here so we don't mess with the CWD on subprocess if self.use_clipboard: daemonize() else: # exit here instead of inside save_file self.on_exit(width, height)
def load_sample_image(self, fname): """Загрузка изображения с образцом чернил в определитель цвета.""" tmppbuf = Pixbuf.new_from_file(fname) self.pixbufCX = tmppbuf.get_width() self.pixbufCY = tmppbuf.get_height() # создаём новый, строго заданного формата, мало ли что там загрузилось self.pixbuf = Pixbuf.new(GdkPixbuf.Colorspace.RGB, False, 8, self.pixbufCX, self.pixbufCY) # на случай наличия альфа-канала в исходном изображении self.pixbuf.fill(0xffffffff) tmppbuf.composite(self.pixbuf, 0, 0, self.pixbufCX, self.pixbufCY, 0, 0, 1.0, 1.0, GdkPixbuf.InterpType.TILES, 255); self.pixbufPixels = self.pixbuf.get_pixels() self.pixbufChannels = self.pixbuf.get_n_channels() self.pixbufRowStride = self.pixbuf.get_rowstride() #self.swImgView.set_max_content_width(self.pixbufCX) #self.swImgView.set_max_content_height(self.pixbufCY) self.imgView.set_from_pixbuf(self.pixbuf) self.swImgView.get_hadjustment().set_value(0) self.swImgView.get_vadjustment().set_value(0) # self.lstoreSamples.clear() self.update_sample_count() self.compute_average_color()
def gen_preview(text, size=9, opacity=1) -> Pixbuf: pix = Pixbuf.new(Colorspace.RGB, True, 8, 60, 80) surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, pix.get_width(), pix.get_height()) context = cairo.Context(surface) # Gdk.cairo_set_source_pixbuf(context, pix, 0, 0) # context.paint() # paint the pixbuf # context.select_font_face('sans-serif') context.set_font_size(size) # Document background grad = cairo.LinearGradient(0, 0, 0, pix.get_height()) grad.add_color_stop_rgb(0, 0.95, 0.95, 0.95) grad.add_color_stop_rgb(pix.get_height(), 0.93, 0.93, 0.93) context.set_source(grad) context.paint_with_alpha(opacity) # Document Outline grad = cairo.LinearGradient(0, 0, 0, pix.get_height()) grad.add_color_stop_rgba(0, 1, 1, 1, opacity) grad.add_color_stop_rgba(pix.get_height(), 0.94, 0.94, 0.94, opacity) context.rectangle(1, 1, pix.get_width() - 2, pix.get_height() - 2) context.set_source(grad) context.stroke() # Border context.rectangle(0, 0, pix.get_width(), pix.get_height()) context.set_source_rgba(0.9, 0.9, 0.9, opacity) context.stroke() # add the text for num, line in enumerate(text.split('\n'), 1): context.set_source_rgba(0.2, 0.2, 0.24, opacity) # Fix to remove \r if it exists if line.startswith('\r'): line = line[1:] if num == 1: context.select_font_face('sans-serif', cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_BOLD) else: context.select_font_face('monospace', cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL) context.move_to(4, 4 + size * num) context.show_text(line) # get the resulting pixbuf surface = context.get_target() return Gdk.pixbuf_get_from_surface(surface, 0, 0, surface.get_width(), surface.get_height())
def crop_borders(self): if not isinstance(self._buffer, Pixbuf) or self.path is None: return self bbox = self._compute_borders_crop_bbox() # Crop is possible if computed bbox is included in pixbuf if bbox[2] - bbox[0] < self.width or bbox[3] - bbox[1] < self.height: pixbuf = Pixbuf.new(Colorspace.RGB, self._buffer.get_has_alpha(), 8, bbox[2] - bbox[0], bbox[3] - bbox[1]) self._buffer.copy_area(bbox[0], bbox[1], bbox[2] - bbox[0], bbox[3] - bbox[1], pixbuf, 0, 0) return Imagebuf(self.path, pixbuf, pixbuf.get_width(), pixbuf.get_height()) return self
def _create_thumbnail(self, source_file, thumbnail_filename): # Cannot access source; create neither thumbnail nor fail file if not os.access(source_file, os.R_OK): return False try: image = Pixbuf.new_from_file_at_scale(source_file, self.thumb_size, self.thumb_size, True) dest_path = self._get_thumbnail_path(thumbnail_filename) success = True except GError: image = Pixbuf.new(GdkPixbuf.Colorspace.RGB, False, 8, 1, 1) dest_path = self._get_fail_path(thumbnail_filename) success = False width = 0 height = 0 try: with Image.open(source_file) as img: width = img.size[0] height = img.size[1] except IOError: pass options = { "tEXt::" + self.KEY_URI: str(self._get_source_uri(source_file)), "tEXt::" + self.KEY_MTIME: str(self._get_source_mtime(source_file)), "tEXt::" + self.KEY_SIZE: str(os.path.getsize(source_file)) } if width > 0 and height > 0: options["tEXt::" + self.KEY_WIDTH] = str(width) options["tEXt::" + self.KEY_HEIGHT] = str(height) # First create temporary file and then move it. This avoids problems # with concurrent access of the thumbnail cache, since "move" is an # atomic operation handle, tmp_filename = tempfile.mkstemp(dir=self.base_dir) os.close(handle) os.chmod(tmp_filename, 0o600) image.savev(tmp_filename, "png", list(options.keys()), list(options.values())) os.replace(tmp_filename, dest_path) return success
def __init__(self, data, size, palette): if not data: # dummy image self.pixbuf = Pixbuf.new(Colorspace.RGB, False, 8, size[0] * 8 * 2, size[1] * 8 * 2) self.pixbuf.fill(0xAAAAAAFF) return # slice into blocks blocks = cut(data, BLOCK) # slice block content blocks = [cut(b, ROW) for b in blocks] # rearrange into blockrows (y/x coordinates) blocks = cut(blocks, size[0]) bytestring = [] # for each block row for y in range(0, size[1]): # for each final row for i in range(0, int(BLOCK / ROW)): # for each block column for x in range(0, size[0]): r = blocks[y][x][i] # extract pixels from rows for j in range(4): bytestring.append(r[j] & 0x0F) # first (....AAAA) bytestring.append(r[j] >> 4) # second (BBBB....) # apply palette result = [] for i in bytestring: result += palette.colors[i] # get result in binary format result = b''+bytearray(result) # create image self.pixbuf = Pixbuf.new_from_data( result, 0, False, 8, 8 * size[0], 8 * size[1], 8 * size[0] * 3, None, None) self.pixbuf = self.pixbuf.scale_simple( 8 * size[0] * 2, 8 * size[1] * 2, InterpType.NEAREST)
def color_sample_add(self, x, y): colorv = self.cursorSampler(x, y) if colorv is None: return itr = self.color_sample_find_itr(colorv) if (itr is None) and (self.lstoreSamples.iter_n_children() < self.MAX_COLOR_SAMPLES): pbuf = Pixbuf.new(GdkPixbuf.Colorspace.RGB, False, 8, self.samplePixbufSize, self.samplePixbufSize) pbuf.fill(int(colorv)) itr = self.lstoreSamples.append((colorv, 'R=%d, G=%d, B=%d (%s)' % (*colorv.get_values(), colorv.hexv), pbuf)) self.ivSamples.select_path(self.lstoreSamples.get_path(itr)) self.update_sample_count() self.compute_average_color()
def create_doubled_pixbuf(frompixbuf): # создаём "сдвоенную" иконку из обычной cx = frompixbuf.get_width() cy = frompixbuf.get_height() pixbuf = Pixbuf.new(Colorspace.RGB, True, 8, cx, cy) pixbuf.fill(0xff00ff00) scale = 0.75 ncx = int(round(cx * scale)) ncy = int(round(cy * scale)) frompixbuf.composite(pixbuf, 0, 0, ncx, ncy, 0, 0, scale, scale, InterpType.HYPER, 255) cx -= ncx cy -= ncy frompixbuf.composite(pixbuf, cx, cy, ncx, ncy, cx, cy, scale, scale, InterpType.HYPER, 255) return pixbuf
## Add a row without own image (easy) treestore.append(None, [None, "data without own image"]) ## Add a row with a stock icon (also easy) iconpixbuf = Gtk.IconTheme.get_default().load_icon("./images", 16, 0) treestore.append(None, [iconpixbuf, "data with a stock icon"]) ## Add a row with an image from disk (still easy, uncomment if you have a suitable image file) #loadedpixbuf = Pixbuf.new_from_file_at_size("../../img/logo.png", 125, 125) #treestore.append(None, [loadedpixbuf, "data with a custom image from disk"]) ## Add a row with a flat-painted image (easy, but not always useful...) from gi.repository import GLib, Gtk, Gdk, GObject filledpixbuf = Pixbuf.new(Colorspace.RGB, True, 8, 16, 16) ## In fact, it is RGBA filledpixbuf.fill(0xff9922ff) treestore.append(None, [filledpixbuf, "data with a custom color filled image"]) ## Add a row with a custom-drawn image (cannot do) import cairo # one "cairo" is not ... #from gi.repository import cairo # ... the other "cairo" (which does not even contain ImageSurface) drawnpixbuf = filledpixbuf.copy() img = cairo.ImageSurface(cairo.FORMAT_ARGB32, 50, 50) cc = cairo.Context(img) cc.set_source_rgb(.8, .0, .0) # Cairo uses floats from 0 to 1 for RGB values cc.rectangle(5, 5, 5, 30) cc.fill() cc.set_source_rgb(.5, .9, .2) cc.move_to(5, 5) ## (not visible, but this is a minor problem) cc.line_to(100, 100)
def create_blank_pixbuf(size=16): pix = Pixbuf.new(Colorspace.RGB, True, 8, size, size) pix.fill(0x0) return pix
def __init__(self): # self.emacs = which('emacs') self.emacsProcess = None self.cfg = Config() self.cursorSamplers = (('rbtnCursorPixel', self.get_pixbuf_pixel_color), ('rbtnCursorBoxIntenseColor', self.get_pixbuf_intense_color)) self.cfg.maxPixelSamplerMode = len(self.cursorSamplers) - 1 self.cfg.load() resldr = get_resource_loader() uibldr = get_gtk_builder(resldr, 'inktools.ui') Gtk.Settings.get_default().set_property('gtk-application-prefer-dark-theme', self.DARK_THEME) self.sampleFillColor = self.SAMPLE_FILL_DARK if self.DARK_THEME else self.SAMPLE_FILL_LIGHT self.db = None self.rndchooser = None self.stats = None # для выбора в случайном выбираторе self.includetags = set() self.excludetags = set() __LOGO = 'inktools.svg' # # очень важное окно # self.aboutDialog = uibldr.get_object('aboutDialog') logoSize = WIDGET_BASE_HEIGHT * 10 self.aboutDialog.set_logo(resldr.load_pixbuf(__LOGO, logoSize, logoSize)) self.aboutDialog.set_program_name(TITLE) self.aboutDialog.set_version(TITLE_VERSION) self.aboutDialog.set_copyright(COPYRIGHT) self.aboutDialog.set_website(URL) self.aboutDialog.set_website_label(URL) # # основное окно # self.window = uibldr.get_object('inkavailwnd') icon = resldr.load_pixbuf_icon_size(__LOGO, Gtk.IconSize.DIALOG, 'computer') self.window.set_icon(icon) self.headerbar = uibldr.get_object('headerbar') self.headerbar.set_title(TITLE_VERSION) # self.pages, self.pageStatistics, self.pageChooser, self.pageSampler = get_ui_widgets(uibldr, 'pages', 'pageStatistics', 'pageChooser', 'pageSampler') _, self.samplePixbufSize, _ = Gtk.IconSize.lookup(Gtk.IconSize.MENU) # # страница статистики # self.nocoloricon = resldr.load_pixbuf_icon_size( 'nocolor-dark.svg' if self.DARK_THEME else 'nocolor.svg', Gtk.IconSize.MENU, 'dialog-question-symbolic') self.totalstatlstore, self.totalstatview = get_ui_widgets(uibldr, 'totalstatlstore', 'totalstatview') self.detailstats = TreeViewShell.new_from_uibuilder(uibldr, 'detailstatsview') detailstatswnd = uibldr.get_object('detailstatswnd') # костылинг detailstatswnd.set_min_content_height(WIDGET_BASE_HEIGHT * 24) self.openorgfiledlg = uibldr.get_object('openorgfiledlg') # # главное меню # # грязный хакЪ из-за ошибки в Glade 3.22.2, криво генерирующей элементы меню со значками # пока это всё оторву - пущай будет человеческое меню, а не гномье #mnuFile = uibldr.get_object('mnuFile') #img = Gtk.Image.new_from_icon_name('open-menu-symbolic', Gtk.IconSize.MENU) #mnuFile.add(img) self.mnuFileOpenRecent = uibldr.get_object('mnuFileOpenRecent') # # страница случайного выбора чернил # self.randominkname, self.randominktags, self.randominkdesc,\ self.randominkavail, self.randominkstatus,\ self.randominkcolorimg, self.randominkcolordesc, self.randominkmaincolor,\ self.randominkusagecnt = get_ui_widgets(uibldr, 'randominkname', 'randominktags', 'randominkdesc', 'randominkavail', 'randominkstatus', 'randominkcolorimg', 'randominkcolordesc', 'randominkmaincolor', 'randominkusagecnt') self.randominkdescbuf = self.randominkdesc.get_buffer() uibldr.get_object('randominkusagesw').set_size_request(-1, WIDGET_BASE_HEIGHT * 6) self.randominkusageview = TreeViewShell.new_from_uibuilder(uibldr, 'randominkusagetv') self.randominkusageview.sortColumn = 0 self.includetagstxt, self.excludetagstxt, self.tagchooserdlg = get_ui_widgets(uibldr, 'includetagstxt', 'excludetagstxt', 'tagchooserdlg') self.randominkColorPixbuf = Pixbuf.new(GdkPixbuf.Colorspace.RGB, False, 8, self.samplePixbufSize, self.samplePixbufSize) self.tagchecklistbox = CheckListBox(selectionbuttons=True) # костыль # потому что set_min_content_width с какого-то хрена не работает self.tagchecklistbox.scwindow.set_size_request(WIDGET_BASE_WIDTH * 80, WIDGET_BASE_HEIGHT * 10) #self.tagchecklistbox.scwindow.set_min_content_width(WIDGET_BASE_WIDTH * 90) #self.tagchecklistbox.scwindow.set_min_content_height(WIDGET_BASE_HEIGHT * 16) taglistvbox = self.tagchooserdlg.get_content_area() taglistvbox.pack_start(self.tagchecklistbox, True, True, 0) self.chosenInk = None # # страница подбора среднего цвета # self.btnImageFile = uibldr.get_object('btnImageFile') self.btnImageFile.set_current_folder(self.cfg.imageSampleDirectory) self.swImgView, self.imgView, self.ebImgView,\ self.labCursorRGBX, self.imgLens, self.imgCursorColor = get_ui_widgets(uibldr, 'swImgView', 'imgView', 'ebImgView', 'labCursorRGBX', 'imgLens', 'imgCursorColor') self.pixbuf = None self.pixbufPixels = None self.pixbufChannels = 0 self.pixbufRowStride = 0 self.pixbufCX = 0 self.pixbufCY = 0 self.imgViewOX = 0 self.imgViewOY = 0 # # курсор цветовыбиралки # self.pixbufCursorImgView = resldr.load_pixbuf('cursor.png', None, None) self.cursorImgViewOX = 7 self.cursorImgViewOY = 7 # будут присвоены потом, когда ebImgView заимеет Gdk.Window self.cursorOutOfImgView = None self.cursorImgView = None self.ebImgView.add_events(Gdk.EventMask.BUTTON_PRESS_MASK\ | Gdk.EventMask.POINTER_MOTION_MASK\ | Gdk.EventMask.LEAVE_NOTIFY_MASK) self.lensPixbuf = Pixbuf.new(GdkPixbuf.Colorspace.RGB, False, 8, self.LENS_CX, self.LENS_CY) self.lensPixbuf.fill(self.sampleFillColor) self.imgLens.set_from_pixbuf(self.lensPixbuf) # swSamples, self.ivSamples, self.lstoreSamples, self.labNSamples,\ self.btnSampleRemove = get_ui_widgets(uibldr, 'swSamples', 'ivSamples', 'lstoreSamples', 'labNSamples', 'btnSampleRemove') self.itrSelectedSample = None self.cursorSampler = self.get_pixbuf_pixel_color for ix, (rbtnn, sampler) in enumerate(self.cursorSamplers): # rbtn = uibldr.get_object(rbtnn) if ix == self.cfg.pixelSamplerMode: rbtn.set_active(True) rbtn.connect('toggled', self.rbtnCursorSamplerMode_toggled, ix) self.cursorSampler = self.cursorSamplers[self.cfg.pixelSamplerMode][-1] self.swImgView.set_min_content_width(WIDGET_BASE_WIDTH * 48) swSamples.set_min_content_height(WIDGET_BASE_HEIGHT * 6) swSamples.set_size_request(WIDGET_BASE_WIDTH * 24, -1) # self.cursorColorPixbuf = Pixbuf.new(GdkPixbuf.Colorspace.RGB, False, 8, self.samplePixbufSize, self.samplePixbufSize) self.cursorColorPixbuf.fill(self.sampleFillColor) self.imgCursorColor.set_from_pixbuf(self.nocoloricon) # self.averageColorPixbuf = Pixbuf.new(GdkPixbuf.Colorspace.RGB, False, 8, self.samplePixbufSize, self.samplePixbufSize) self.averageColorPixbuf.fill(self.sampleFillColor) self.imgAverageColor, self.labAverageRGBX, self.btnCopy = get_ui_widgets(uibldr, 'imgAverageColor','labAverageRGBX','btnCopy') self.imgAverageColor.set_from_pixbuf(self.averageColorPixbuf) self.clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD) # self.window.show_all() self.load_window_state() self.update_recent_files_menu() uibldr.connect_signals(self) self.load_db()
def load_db(self): self.totalstatview.set_model(None) self.totalstatlstore.clear() self.detailstats.view.set_model(None) self.detailstats.store.clear() expand = [] def __bool_s(b, clr=None): st = '√' if clr is None else '<span color="%s"><b>√</b></span>' % clr return st if b else '' CTODO = '#fd0' CNODO = '#c00' CDONE = '#0f0' try: self.db = load_ink_db(self.cfg.databaseFileName) self.stats = get_ink_stats(self.db) self.rndchooser = None # статистика if self.stats: dfname = os.path.split(self.cfg.databaseFileName)[-1] # # общая статистика # totals = self.stats.get_total_result_table() for row in totals: self.totalstatlstore.append(row) # # детали # for tagstat in self.stats.tagStats: itr = self.detailstats.store.append(None, (None, tagstat.title, '', '', '', '', None, None)) expand.append(self.detailstats.store.get_path(itr)) _items = tagstat.stats.items() # мелкий костылинг: сортироваться должны только списки, # полученные обработкой директив TAGSTATS if tagstat.issortable: # порядок сортировки: название метки, наличие # на кой чорт питонщики сделали key вместо cmp? # в случае cmp не пришлось бы тратить память на # значение временного ключа сортировки # и фрагментировать кучу def __group_key_f(r): return '%5d%s' % (r[1].available, self.stats.get_tag_display_name(r[0]).lower()) _items = sorted(_items, key=__group_key_f, reverse=True) for tag, nfo in _items: row = (None, self.stats.get_tag_display_name(tag), *nfo.counter_strs(), None, None) subitr = self.detailstats.store.append(itr, row) # конкретные марки чернил сортируем уже по названию в алфавитном порядке for ink in sorted(nfo.inks, key=lambda i: i.text.lower()): if ink.color: pbuf = Pixbuf.new(GdkPixbuf.Colorspace.RGB, False, 8, self.samplePixbufSize, self.samplePixbufSize) pbuf.fill(int(ColorValue.get_rgb32_value(ink.color))) else: pbuf = self.nocoloricon # 'название', 'отсортированный список человекочитаемых меток', 'описание', 'наличие' _inkname, _inktags, _inkdesc, _inkavail = self.stats.get_ink_description(ink) hint = ['<b>%s</b>' % markup_escape_text(_inkname)] if ink.color: hint.append('Цвет: <span color="#%.6x">██</span> %s' % (ink.color, markup_escape_text(ColorValue.new_from_rgb24(ink.color).get_description()))) if _inkdesc: hint.append(markup_escape_text(_inkdesc)) if _inkavail: hint.append('В наличии: %s' % markup_escape_text(_inkavail)) if ink.done == False: hint.append('Запланирована покупка этих чернил') if ink.missing: hint.append('Отсутствуют данные: %s' % self.stats.get_ink_missing_data_str(ink)) bunwanted = ink.done is None self.detailstats.store.append(subitr, (ink, ink.text, # avail __bool_s(ink.avail, CDONE), # unavail __bool_s(not ink.avail, None if bunwanted else CTODO), # wanted __bool_s(ink.done == False, CTODO), # прямое сравнение, т.к. иначе None будет воспринято тоже как False # unwanted __bool_s(bunwanted, CNODO), pbuf, '\n\n'.join(hint))) self.rndchooser = RandomInkChooser(self.stats, None, None) else: dfname = '' # # метки # self.tagchecklistbox.clear_items() def __add_tag(tagname): tagdisp = tagname if tagname not in self.stats.tagNames else self.stats.tagNames[tagname] self.tagchecklistbox.add_item(False, tagdisp, tagname) # в первую очередь используем только метки, учитываемые в статистике ntags = 0 for tsinfo in self.stats.tagStats: for tagname in sorted(tsinfo.tags): __add_tag(tagname) ntags += 1 # ...а вот если там меток не было - берём весь список меток if not ntags: for tagname in sorted(self.stats.tags): __add_tag(tagname) self.includetags.clear() # пустое множество - выбирать все self.excludetags.clear() # пустое множество - не исключать ничего self.includetagstxt.set_text(self.INCLUDE_ANY) self.excludetagstxt.set_text(self.EXCLUDE_NOTHING) # self.openorgfiledlg.select_filename(self.cfg.databaseFileName) self.headerbar.set_subtitle(dfname) self.cfg.add_recent_file(self.cfg.databaseFileName) self.update_recent_files_menu() finally: self.detailstats.view.set_model(self.detailstats.store) for path in expand: self.detailstats.view.expand_row(path, False) self.totalstatview.set_model(self.totalstatlstore) self.choose_random_ink()
window.set_size_request(500, 500) ## Add a row without own image (easy) treestore.append(None, [None, "data without own image"]) ## Add a row with a stock icon (also easy) iconpixbuf = Gtk.IconTheme.get_default().load_icon("folder", 16, 0) treestore.append(None, [iconpixbuf, "data with a stock icon"]) ## Add a row with an image from disk (still easy, uncomment if you have a suitable image file) #loadedpixbuf = Pixbuf.new_from_file_at_size("../../img/logo.png", 125, 125) #treestore.append(None, [loadedpixbuf, "data with a custom image from disk"]) ## Add a row with a flat-painted image (easy, but not always useful...) from gi.repository import Gtk, Gdk filledpixbuf = Pixbuf.new(Colorspace.RGB, True, 8, 16, 16) ## In fact, it is RGBA filledpixbuf.fill(0xff9922ff) treestore.append(None, [filledpixbuf, "data with a custom color filled image"]) px = PixbufLoader.new_with_type('pnm') #color = b'\xee\xff\x2d' #px.write(b'P6\n\n1 1\n255\n' + color) #px.write(color) iconpnm = b"""P2 24 7 25 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 3 3 3 3 0 0 7 7 7 7 0 0 11 11 11 11 0 0 15 15 15 15 0 0 3 0 0 0 0 0 7 0 0 0 0 0 11 0 0 0 0 0 15 0 0 15 0 0 3 3 3 0 0 0 7 7 7 0 0 0 11 11 11 0 0 0 15 15 15 15 0 0 3 0 0 0 0 0 7 0 0 0 0 0 11 0 0 0 0 0 15 0 0 0 0 0 3 0 0 0 0 0 7 7 7 7 0 0 11 11 11 11 0 0 15 0 0 0 0
def populateTreeStore(self, treeStore, parent_row=None, reset_path=None): # {{{ ## without any parent specified, rows will be added to the very left of the TreeView, ## otherwise they will become childs thereof if parent_row is None: if reset_path is not None: basepath = reset_path else: if self.row_prop(self.tsFiles.get_iter_first(), 'rowtype') == 'updir': basepath = self.row_prop(self.tsFiles.get_iter_first(), 'filepath') else: raise AttributeError( 'Specify either parent_row, reset_path, or ensure the first row is of "updir" type' ) w('window1').set_title('PlotCommander: %s' % basepath) ## On startup, or when the 'updir' node is selected, we update the whole tree. ## Initially, it has to be cleared of all rows. ## During this operation, its selection will change, but the plots should not be updated so that it is fast. self.lockTreeViewEvents = True self.tsFiles.clear( ) ## TODO: remember the unpacked rows, and also the selected ones self.clearAllPlotIcons( self.tsFiles.get_iter_first()) ## TODO: obsolete, rm! self.lockTreeViewEvents = False ## The first node of cleared treeStore will point to the above directory, enabling one to browse whole filesystem plotstyleIcon = Pixbuf.new(Colorspace.RGB, True, 8, 10, 10) plotstyleIcon.fill(0xffffffff) currentIter = treeStore.append(None, [ basepath, self.rowtype_icon('updir'), '..', plotstyleIcon, None, None, 'updir' ]) ## ^^ FIXME basepath? or os.path.dirname(basepath) ? treeStore.append(currentIter, self.dummy_treestore_row) elif parent_row is not None and reset_path is None: ## If not resetting the whole tree, get the basepath from the parent row basepath = treeStore.get_value(parent_row, self.treeStoreColumns['filepath']) else: raise AttributeError() ## Prepare the lists of paths, column numbers and spreadsheet numbers to be added parentrowtype = self.row_prop(parent_row, 'rowtype') if parent_row else 'dir' assert not self.rowtype_is_leaf(parentrowtype) if parentrowtype == 'dir': ## Populate a directory with files/subdirs ## Get the directory contents and sort it alphabetically filenames = os.listdir(basepath) filenames = sorted(filenames, key=sort_alpha_numeric.split_alpha_numeric ) # intelligent alpha/numerical sorting fileFilterString = w('enFileFilter').get_text().strip() itemFullNames = [ os.path.join(basepath, filename) for filename in filenames ] # add the full path # dirs will be listed first and files below; filter the files ## FIXME: filter only through the file name, not full path! itemFullNames = [f for f in itemFullNames if self.is_dir(f)] + \ [f for f in itemFullNames if (not self.is_dir(f) and (fileFilterString == '' or re.findall(fileFilterString, f)))] itemShowNames = [os.path.split(f)[1] for f in itemFullNames ] # only file name without path will be shown columnNumbers = [None] * len( itemFullNames ) # obviously files/subdirs are assigned no column number spreadNumbers = [None] * len( itemFullNames) # nor they are assigned any spreadsheet number rowTypes = [self.row_type_from_fullpath(f) for f in itemFullNames] elif parentrowtype == 'csvmulticolumn': ## Note: Multicolumn means at least 3 columns (i.e. x-column and two or more y-columns) data_array, header, parameters = robust_csv_parser.loadtxt( basepath, sizehint=10000) columnFilterString = w('enColFilter').get_text().strip() columnNumbers, header = zip(*[ n for n in enumerate(header) if re.findall(columnFilterString, n[1]) ]) ## filter the columns #FIXME File "/home/dominecf/p/plotcommander/plotcommander.py", line 303, in populateTreeStore #columnNumbers, header = zip(*[n for n in enumerate(header) if re.findall(columnFilterString, n[1])]) ## filter the columns #ValueError: not enough values to unpack (expected 2, got 0) itemFullNames = [basepath] * len( header) # all columns are from one file itemShowNames = header # column numbers are either in file header, or auto-generated spreadNumbers = [None] * len( header) # there are no spreadsheets in CSV files rowTypes = ['csvcolumn'] * len(header) elif parentrowtype == 'opjfile': print("parentrowtype == 'opjfile':", basepath) opj = self.origin_parse_or_cache(basepath) ## Add "graphs" - which show the selected columns in presentation-ready format ## Fixme support for multiple opjlayers also here def generate_graph_annotation(graph): layerNumber = 0 ## Fixme support for multiple opjlayers: ["graphs"][1].layers[0].curves[3].xColumnName legend_box = self.decode_origin_label( graph.layers[0].legend.text, splitrows=True) comment = "" for legendline in legend_box: ## the legend may have format as such: ['\l(1) 50B', '\l(2) 48B', ...], needs to be pre-formatted: newline = re.sub(r'\\l\(\d\)\s', '', legendline) if newline == legendline: comment += newline + ' ' return comment itemShowNames = [ '%s; name: %s; label: %s' % (self.decode_origin_label( graph.name), self.decode_origin_label( graph.label), generate_graph_annotation(graph)) for graph in opj['graphs'] ] itemFullNames = [basepath] * len( itemShowNames) # all columns are from one file columnNumbers = [None] * len(itemShowNames) spreadNumbers = list(range(len(itemShowNames))) rowTypes = ['opjgraph'] * len(itemShowNames) ## Add "columns" - which enable to access all data in the file, including those not used in "graphs" for spread in opj['spreads']: print(spread.label, self.decode_origin_label(spread.label)) itemShowNames = itemShowNames + [ '%s "%s"' % (self.decode_origin_label( spread.name), self.decode_origin_label(spread.label)) for spread in opj['spreads'] ] itemFullNames = itemFullNames + [basepath] * len( itemShowNames) # all columns are from one file columnNumbers = columnNumbers + [None] * len(itemShowNames) spreadNumbers = spreadNumbers + list(range(len(itemShowNames))) rowTypes = rowTypes + ['opjspread'] * len(itemShowNames) elif parentrowtype == 'opjspread': opj = self.origin_parse_or_cache(basepath) parent_spreadsheet = self.row_prop(parent_row, 'spreadsheet') itemShowNames = [ self.decode_origin_label(column.name) for column in opj['spreads'][parent_spreadsheet].columns ] itemFullNames = [basepath] * len( itemShowNames) # all columns are from one file columnNumbers = list(range(len(itemShowNames))) spreadNumbers = [parent_spreadsheet] * len(itemShowNames) rowTypes = ['opjcolumn'] * len(itemShowNames) elif parentrowtype == 'opjgraph': opj = self.origin_parse_or_cache(basepath) parent_graph = self.row_prop( parent_row, 'spreadsheet' ) ## The key 'spreadsheet' is misused here to mean 'graph' layerNumber = 0 ## Fixme support for multiple opjlayers: ["graphs"][1].layers[0].curves[3].xColumnName ## Try to extract meaningful legend for each curve, assuming the legend box has the same number of lines curves = opj['graphs'][parent_graph].layers[layerNumber].curves legend_box = self.decode_origin_label( opj['graphs'][parent_graph].layers[layerNumber].legend.text, splitrows=True) legends = [] for legendline in legend_box: ## the legend may have format as such: ['\l(1) 50B', '\l(2) 48B', ...], needs to be pre-formatted: newline = re.sub(r'\\l\(\d\)\s', '', legendline) if newline != legendline: legends.append(newline) legends = legends[:len(curves)] + ( [''] * (len(curves) - len(legends)) ) ## trim or extend the legends to match the curves itemShowNames, itemFullNames, columnNumbers, spreadNumbers = [], [], [], [] for curve, legend in zip(curves, legends): ## FIXME add support for xColumn different than the first one in Spreadsheet, also here #print("curve t xCol yCol:", curve.dataName.self.decode_origin_label('utf-8'), #curve.xColumnName.self.decode_origin_label('utf-8'), curve.yColumnName.self.decode_origin_label('utf-8')) #print([spread.name for spread in opj['spreads']], (curve.dataName[2:])) ## Seek the corresponding spreadsheet and column by their name spreadsheet_index = [spread.name for spread in opj['spreads'] ].index(curve.dataName[2:]) spread = opj['spreads'][spreadsheet_index] y_column_index = [column.name for column in spread.columns ].index(curve.yColumnName) x_column_index = [column.name for column in spread.columns ].index(curve.xColumnName) #print(curve.dataName[2:].self.decode_origin_label('utf-8'), spreadsheet_index, curve.yColumnName.self.decode_origin_label('utf-8'), y_column_index) itemShowNames.append( '%s -> spread %s: column %s (against %s)' % (legend, self.decode_origin_label(spread.name), self.decode_origin_label( spread.columns[y_column_index].name), self.decode_origin_label( spread.columns[x_column_index].name))) itemFullNames.append(basepath) # all columns are from one file columnNumbers.append(y_column_index) spreadNumbers.append(spreadsheet_index) rowTypes = ['opjcolumn'] * len( itemShowNames) ## TODO or introduce opjgraphcurve ? else: warnings.warn( 'Not prepared yet to show listings of this file: %s' % parentrowtype) return ## Go through all items and populate the node for itemFullName, itemShowName, columnNumber, spreadNumber, rowtype in \ zip(itemFullNames, itemShowNames, columnNumbers, spreadNumbers, rowTypes): plotstyleIcon = Pixbuf.new(Colorspace.RGB, True, 8, 10, 10) plotstyleIcon.fill(0xffffffff) currentIter = treeStore.append(parent_row, [ itemFullName, self.rowtype_icon(rowtype), itemShowName, plotstyleIcon, columnNumber, spreadNumber, rowtype ]) if not self.rowtype_is_leaf( rowtype): ## TODO row---> parentrowtype treeStore.append( currentIter, self.dummy_treestore_row ) # shows the "unpacking arrow" left of the item